Skip to content
Klozzo API
OpenAPI Postman

Reference

Contacts

Contacts are the core CRM record: leads, customers, and partners owned by the authenticated user. All endpoints scope by user ownership; cross-user access is rejected with 403/404 depending on policy.

List contact properties

Lists every property a contact can carry in this location: the native ones (sent at the root of the payload) and this location's custom fields (sent inside custom_field_values, keyed by key, uuid or numeric id).

GET /api/contacts/properties

It exists because otherwise the only way to know what you may send is to read the source: /api/custom-fields lists only the custom ones, and the native ones were written down nowhere. Each entry says where it goes — location is root for top-level fields and custom_field_values for the rest — and which ones cannot be written here, with the alternative route when there is one.

curl --request GET \
    --get "https://klozzo.com/api/contacts/properties" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
{
    "data": [
        {
            "key": "first_name",
            "label": "Nombre",
            "type": "text",
            "group": "native",
            "location": "root",
            "writable": true,
            "required": true
        }
    ]
}
  • data object[]

    • key string

    • label string

    • type string

    • group string

    • location string

    • writable boolean

    • required boolean

List lead stages

Lists the lead stages this account defines — the values lead_stage_id accepts.

GET /api/lead-stages

Read this before you send lead_stage_id. Lead stages are a catalogue each account defines for itself — not a fixed list shipped with the product — so the numbers are different in every account and there is no way to guess them. Call this endpoint once, keep the mapping, and send the id:

{ "lead_stage_id": 1 }

null leaves the contact with no stage. An id from another account is rejected with 422, which is the failure this endpoint exists to prevent.

They come back in the order the settings screen shows them (position), so you can render your own picker without re-sorting. slug is stable when somebody renames a stage — match on it if you keep a mapping in your own system. contacts_count is informational and moves on its own.

meta.usage repeats the one-line instruction, so a caller that only ever looks at the response body still finds out what to do with the id.

curl --request GET \
    --get "https://klozzo.com/api/lead-stages" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
{
    "data": [
        {
            "id": 1,
            "name": "Nuevo",
            "slug": "nuevo",
            "color": "#22c55e",
            "position": 0,
            "contacts_count": 42
        },
        {
            "id": 2,
            "name": "Contactado",
            "slug": "contactado",
            "color": "#3b82f6",
            "position": 1,
            "contacts_count": 17
        },
        {
            "id": 3,
            "name": "Calificado",
            "slug": "calificado",
            "color": null,
            "position": 2,
            "contacts_count": 4
        }
    ],
    "meta": {
        "usage": "Manda el `id` en `lead_stage_id` al crear o actualizar un contacto: {\"lead_stage_id\": 1}. `null` lo deja sin estado."
    }
}
  • data object[]

    • id integer

    • name string

    • slug string

    • color string

    • position integer

    • contacts_count integer

  • meta object

    • usage string

List contacts

Returns a paginated list of contacts owned by the authenticated user. Supports full-text search across name, email, phone, and company.

GET /api/contacts

Query parameters

  • search string

    Fragment matched against first/last name, email, phone, company.

    Example: ada

  • contact_type string

    Filter by type. One of lead, customer, partner.

    Example: lead

  • sort string

    Column to sort by (e.g. created_at, last_name). Defaults to newest first.

    Example: created_at

  • direction string

    Sort direction, asc or desc.

    Example: desc

  • per_page integer

    Rows per page. Default 25, maximum 100. See Lists, paging and filters.

    Example: 25

curl --request GET \
    --get "https://klozzo.com/api/contacts?search=ada&contact_type=lead&sort=created_at&direction=desc&per_page=25" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
{
    "data": [
        {
            "id": null,
            "ulid": null,
            "first_name": "Morgan",
            "last_name": "Hirthe",
            "full_name": "Morgan Hirthe",
            "email": "dare.emelie@example.com",
            "email_verified_at": null,
            "email_verification_excluded": false,
            "email_verification_sent_at": null,
            "phone": "+12025550100",
            "phone_e164": null,
            "phone_country": "US",
            "phone_type": null,
            "has_whatsapp": null,
            "whatsapp_checked_at": null,
            "whatsapp_history_synced_until": null,
            "whatsapp_history_exhausted": false,
            "avatar_url": null,
            "additional_emails": [],
            "additional_phones": [],
            "company_name": "McLaughlin, Leuschke and Bauch",
            "contact_type": "lead",
            "timezone": "Asia/Famagusta",
            "source": "import",
            "source_number": null,
            "date_of_birth": "2003-07-15",
            "dnd_all": false,
            "dnd_email": false,
            "dnd_sms": false,
            "dnd_calls": false,
            "dnd_voicemail": false,
            "dnd_gmb": null,
            "dnd_fb_messenger": null,
            "dnd_meta": [],
            "last_activity_at": "2026-08-17T14:16:42+00:00",
            "created_at": null,
            "updated_at": null,
            "owner_id": null,
            "lead_stage_id": null
        },
        {
            "id": null,
            "ulid": null,
            "first_name": "Lucienne",
            "last_name": "Haag",
            "full_name": "Lucienne Haag",
            "email": "lwisoky@example.net",
            "email_verified_at": null,
            "email_verification_excluded": false,
            "email_verification_sent_at": null,
            "phone": "+12025550101",
            "phone_e164": null,
            "phone_country": "US",
            "phone_type": null,
            "has_whatsapp": null,
            "whatsapp_checked_at": null,
            "whatsapp_history_synced_until": null,
            "whatsapp_history_exhausted": false,
            "avatar_url": null,
            "additional_emails": [],
            "additional_phones": [],
            "company_name": "Nitzsche-Ankunding",
            "contact_type": "customer",
            "timezone": "Asia/Ho_Chi_Minh",
            "source": "import",
            "source_number": null,
            "date_of_birth": "2022-07-10",
            "dnd_all": false,
            "dnd_email": false,
            "dnd_sms": false,
            "dnd_calls": false,
            "dnd_voicemail": false,
            "dnd_gmb": null,
            "dnd_fb_messenger": null,
            "dnd_meta": [],
            "last_activity_at": "2026-08-03T06:11:58+00:00",
            "created_at": null,
            "updated_at": null,
            "owner_id": null,
            "lead_stage_id": null
        }
    ],
    "links": {
        "first": "/?page=1",
        "last": "/?page=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "links": [
            {
                "url": null,
                "label": "« Previous",
                "page": null,
                "active": false
            },
            {
                "url": "/?page=1",
                "label": "1",
                "page": 1,
                "active": true
            },
            {
                "url": null,
                "label": "Next »",
                "page": null,
                "active": false
            }
        ],
        "path": "/",
        "per_page": 25,
        "to": 2,
        "total": 2
    }
}
  • data object[]

    • id string

    • ulid string

    • first_name string

    • last_name string

    • full_name string

    • email string

    • email_verified_at string

    • email_verification_excluded boolean

    • email_verification_sent_at string

    • phone string

    • phone_e164 string

    • phone_country string

    • phone_type string

    • has_whatsapp string

    • whatsapp_checked_at string

    • whatsapp_history_synced_until string

    • whatsapp_history_exhausted boolean

    • avatar_url string

    • additional_emails array

    • additional_phones array

    • company_name string

    • contact_type string

    • timezone string

    • source string

    • source_number string

    • date_of_birth string

    • dnd_all boolean

    • dnd_email boolean

    • dnd_sms boolean

    • dnd_calls boolean

    • dnd_voicemail boolean

    • dnd_gmb string

    • dnd_fb_messenger string

    • dnd_meta object

    • last_activity_at string

    • created_at string

    • updated_at string

    • owner_id string

    • lead_stage_id string

  • links object

    • first string

    • last string

    • prev string

    • next string

  • meta object

    • current_page integer

    • from integer

    • last_page integer

    • links object[]

      • url string

      • label string

      • page string

      • active boolean

    • path string

    • per_page integer

    • to integer

    • total integer

Create a contact

POST /api/contacts

Coming from a form, an ad callback or an automation? Use POST /api/leads instead. This endpoint answers 422 and drops the whole request when the person already exists — including the campaign they came from and what they asked for this time. /api/leads never rejects somebody who comes back.

Creates a new contact owned by the authenticated user. Use it when you are deliberately writing a record you know is new: a migration, an import script, an internal tool.

Server-side duplicate detection applies: if the location's duplicate_mode = block, a 422 is returned when email or phone collides with an existing contact in the same scope.

Body parameters

  • first_name string required

    Contact first name.

    Example: Ada

  • last_name string

    Contact last name.

    Example: Lovelace

  • email string

    Primary email; unique within scope if duplicate-block is on.

    Example: ada@example.com

  • phone string

    E.164 with the country code — +525555555555. A local number is only accepted together with phone_country; without either the request is rejected. Stored normalised to E.164.

    Example: +525555555555

  • phone_country string

    ISO-3166-1 alpha-2, e.g. MX. Only needed when phone has no + country code; ignored when it does.

    Example: MX

  • phone_type string

    One of mobile, home, work, other.

    Example: mobile

  • company_name string

    Example: Analytical Engines Ltd.

  • company_id integer

    The <code>id</code> of an existing record in the companies table.

    Example: 16

  • contact_type string

    One of lead, customer, partner.

    Example: lead

  • lead_stage_id integer

    The <code>id</code> of an existing record in the lead_stages table.

    Example: 16

  • timezone string

    IANA timezone.

    Example: America/Mexico_City

  • source string

    Free-form acquisition source.

    Example: web

  • channel_integration_id integer

    The <code>id</code> of an existing record in the channel_integrations table.

    Example: 16

  • date_of_birth string

    ISO-8601 date.

    Example: 1815-12-10

  • avatar string

    Image, max 2MB. Only via multipart/form-data.

  • additional_emails string[]

    Up to 5 extra email addresses.

    Example: ["architecto"]

  • additional_phones string[]

    Up to 5 extra numbers, same rules as phone. They share the single phone_country, so send them in E.164 if they are from different countries.

    Example: ["architecto"]

  • dnd_all boolean

    Master Do-Not-Disturb toggle.

    Example: false

  • dnd_email boolean

    Block email for this person. Independent of dnd_all, which blocks everything.

    Example: false

  • dnd_sms boolean

    Block SMS for this person.

    Example: false

  • dnd_calls boolean

    Block outbound calls for this person.

    Example: false

  • dnd_voicemail boolean

    Block leaving voicemail for this person.

    Example: false

  • dnd_gmb boolean

    Block Google Business Messages for this person.

    Example: false

  • dnd_fb_messenger boolean

    Block Facebook Messenger for this person.

    Example: false

  • auto_engage boolean

    Enable automated engagement workflows.

    Example: true

  • custom_field_values object

    Map of custom_field_id => value.

    Example: []

curl --request POST \
    "https://klozzo.com/api/contacts" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "first_name=Ada"\
    --form "last_name=Lovelace"\
    --form "email=ada@example.com"\
    --form "phone=+525555555555"\
    --form "phone_country=MX"\
    --form "phone_type=mobile"\
    --form "company_name=Analytical Engines Ltd."\
    --form "company_id=16"\
    --form "contact_type=lead"\
    --form "lead_stage_id=16"\
    --form "timezone=America/Mexico_City"\
    --form "source=web"\
    --form "channel_integration_id=16"\
    --form "date_of_birth=1815-12-10"\
    --form "additional_emails[]=architecto"\
    --form "additional_phones[]=architecto"\
    --form "dnd_all="\
    --form "dnd_email="\
    --form "dnd_sms="\
    --form "dnd_calls="\
    --form "dnd_voicemail="\
    --form "dnd_gmb="\
    --form "dnd_fb_messenger="\
    --form "auto_engage=1"\
    --form "avatar=@/private/var/folders/yw/tssyhl110hs29p29scc2m0jc0000gn/T/phpn370553gbibdcAe8NFE"
{
    "data": {
        "id": null,
        "ulid": null,
        "first_name": "Christelle",
        "last_name": "Bailey",
        "full_name": "Christelle Bailey",
        "email": "rowan.gulgowski@example.com",
        "email_verified_at": null,
        "email_verification_excluded": false,
        "email_verification_sent_at": null,
        "phone": "+12025550102",
        "phone_e164": null,
        "phone_country": "CO",
        "phone_type": null,
        "has_whatsapp": null,
        "whatsapp_checked_at": null,
        "whatsapp_history_synced_until": null,
        "whatsapp_history_exhausted": false,
        "avatar_url": null,
        "additional_emails": [],
        "additional_phones": [],
        "company_name": "Dach-Gaylord",
        "contact_type": "customer",
        "timezone": "Africa/Dakar",
        "source": "import",
        "source_number": null,
        "date_of_birth": null,
        "dnd_all": false,
        "dnd_email": false,
        "dnd_sms": false,
        "dnd_calls": false,
        "dnd_voicemail": false,
        "dnd_gmb": null,
        "dnd_fb_messenger": null,
        "dnd_meta": [],
        "last_activity_at": null,
        "created_at": null,
        "updated_at": null,
        "owner_id": null,
        "lead_stage_id": null
    }
}
  • data object

    • id string

    • ulid string

    • first_name string

    • last_name string

    • full_name string

    • email string

    • email_verified_at string

    • email_verification_excluded boolean

    • email_verification_sent_at string

    • phone string

    • phone_e164 string

    • phone_country string

    • phone_type string

    • has_whatsapp string

    • whatsapp_checked_at string

    • whatsapp_history_synced_until string

    • whatsapp_history_exhausted boolean

    • avatar_url string

    • additional_emails array

    • additional_phones array

    • company_name string

    • contact_type string

    • timezone string

    • source string

    • source_number string

    • date_of_birth string

    • dnd_all boolean

    • dnd_email boolean

    • dnd_sms boolean

    • dnd_calls boolean

    • dnd_voicemail boolean

    • dnd_gmb string

    • dnd_fb_messenger string

    • dnd_meta object

    • last_activity_at string

    • created_at string

    • updated_at string

    • owner_id string

    • lead_stage_id string

Fetch a contact

Returns one contact by id, with everything stored on it. Returns 404 if the contact does not belong to the authenticated user, 403 if policy denies access.

GET /api/contacts/{id}

Path parameters

  • id integer required

    The contact ID.

    Example: 42

curl --request GET \
    --get "https://klozzo.com/api/contacts/42" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
{
    "data": {
        "id": null,
        "ulid": null,
        "first_name": "Morgan",
        "last_name": "Hirthe",
        "full_name": "Morgan Hirthe",
        "email": "imclaughlin@example.org",
        "email_verified_at": null,
        "email_verification_excluded": false,
        "email_verification_sent_at": null,
        "phone": "+12025550103",
        "phone_e164": null,
        "phone_country": "MX",
        "phone_type": null,
        "has_whatsapp": null,
        "whatsapp_checked_at": null,
        "whatsapp_history_synced_until": null,
        "whatsapp_history_exhausted": false,
        "avatar_url": null,
        "additional_emails": [],
        "additional_phones": [],
        "company_name": "O'Keefe Inc",
        "contact_type": "lead",
        "timezone": "Pacific/Tongatapu",
        "source": "manual",
        "source_number": null,
        "date_of_birth": null,
        "dnd_all": false,
        "dnd_email": false,
        "dnd_sms": false,
        "dnd_calls": false,
        "dnd_voicemail": false,
        "dnd_gmb": null,
        "dnd_fb_messenger": null,
        "dnd_meta": [],
        "last_activity_at": "2026-08-09T18:01:18+00:00",
        "created_at": null,
        "updated_at": null,
        "owner_id": null,
        "lead_stage_id": null
    }
}
  • data object

    • id string

    • ulid string

    • first_name string

    • last_name string

    • full_name string

    • email string

    • email_verified_at string

    • email_verification_excluded boolean

    • email_verification_sent_at string

    • phone string

    • phone_e164 string

    • phone_country string

    • phone_type string

    • has_whatsapp string

    • whatsapp_checked_at string

    • whatsapp_history_synced_until string

    • whatsapp_history_exhausted boolean

    • avatar_url string

    • additional_emails array

    • additional_phones array

    • company_name string

    • contact_type string

    • timezone string

    • source string

    • source_number string

    • date_of_birth string

    • dnd_all boolean

    • dnd_email boolean

    • dnd_sms boolean

    • dnd_calls boolean

    • dnd_voicemail boolean

    • dnd_gmb string

    • dnd_fb_messenger string

    • dnd_meta object

    • last_activity_at string

    • created_at string

    • updated_at string

    • owner_id string

    • lead_stage_id string

Update a contact

Updates one contact by id, changing only the fields you send. Audit log records the diff between old and new values. Avatar is not updatable here (re-upload via store or future endpoint).

PUT /api/contacts/{id}

Path parameters

  • id integer required

    The contact ID.

    Example: 42

Body parameters

  • first_name string

    Example: Ada

  • last_name string

    Example: Lovelace

  • email string

    Example: ada@example.com

  • phone string

    E.164 with the country code — +525555555555. A local number is only accepted together with phone_country; without either the request is rejected. Stored normalised to E.164.

    Example: +525555555555

  • phone_country string

    ISO-3166-1 alpha-2, e.g. MX. Only needed when phone has no + country code; ignored when it does.

    Example: MX

  • phone_type string

    One of mobile, home, work, other.

    Example: mobile

  • additional_phones string[]

    Up to 5 extra numbers, same rules as phone. They share the single phone_country, so send them in E.164 if they are from different countries.

    Example: ["architecto"]

  • company_name string

    Example: Analytical Engines Ltd.

  • company_id integer

    The <code>id</code> of an existing record in the companies table.

    Example: 16

  • contact_type string

    One of lead, customer, partner.

    Example: customer

  • lead_stage_id integer

    The <code>id</code> of an existing record in the lead_stages table.

    Example: 16

  • timezone string

    IANA timezone.

    Example: America/Mexico_City

  • source string

    Example: web

  • date_of_birth string

    ISO-8601 date.

    Example: 1815-12-10

  • dnd_all boolean

    Example: false

  • dnd_email boolean

    Block email for this person. Independent of dnd_all, which blocks everything.

    Example: false

  • dnd_sms boolean

    Block SMS for this person.

    Example: false

  • dnd_calls boolean

    Block outbound calls for this person.

    Example: false

  • dnd_voicemail boolean

    Block leaving voicemail for this person.

    Example: false

  • dnd_gmb boolean

    Block Google Business Messages for this person.

    Example: false

  • dnd_fb_messenger boolean

    Block Facebook Messenger for this person.

    Example: false

  • custom_field_values object

    Map of custom_field_id => value.

    Example: []

curl --request PUT \
    "https://klozzo.com/api/contacts/42" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"first_name\": \"Ada\",
    \"last_name\": \"Lovelace\",
    \"email\": \"ada@example.com\",
    \"phone\": \"+525555555555\",
    \"phone_country\": \"MX\",
    \"phone_type\": \"mobile\",
    \"additional_phones\": [
        \"architecto\"
    ],
    \"company_name\": \"Analytical Engines Ltd.\",
    \"company_id\": 16,
    \"contact_type\": \"customer\",
    \"lead_stage_id\": 16,
    \"timezone\": \"America\\/Mexico_City\",
    \"source\": \"web\",
    \"date_of_birth\": \"1815-12-10\",
    \"dnd_all\": false,
    \"dnd_email\": false,
    \"dnd_sms\": false,
    \"dnd_calls\": false,
    \"dnd_voicemail\": false,
    \"dnd_gmb\": false,
    \"dnd_fb_messenger\": false,
    \"custom_field_values\": []
}"
{
    "data": {
        "id": null,
        "ulid": null,
        "first_name": "Christelle",
        "last_name": "Bailey",
        "full_name": "Christelle Bailey",
        "email": "jdach@example.org",
        "email_verified_at": null,
        "email_verification_excluded": false,
        "email_verification_sent_at": null,
        "phone": "+12025550104",
        "phone_e164": null,
        "phone_country": "AR",
        "phone_type": null,
        "has_whatsapp": null,
        "whatsapp_checked_at": null,
        "whatsapp_history_synced_until": null,
        "whatsapp_history_exhausted": false,
        "avatar_url": null,
        "additional_emails": [],
        "additional_phones": [],
        "company_name": "Runte-Considine",
        "contact_type": "lead",
        "timezone": "America/Indiana/Tell_City",
        "source": "web",
        "source_number": null,
        "date_of_birth": "1985-10-22",
        "dnd_all": false,
        "dnd_email": false,
        "dnd_sms": false,
        "dnd_calls": false,
        "dnd_voicemail": false,
        "dnd_gmb": null,
        "dnd_fb_messenger": null,
        "dnd_meta": [],
        "last_activity_at": "2026-07-31T16:34:00+00:00",
        "created_at": null,
        "updated_at": null,
        "owner_id": null,
        "lead_stage_id": null
    }
}
  • data object

    • id string

    • ulid string

    • first_name string

    • last_name string

    • full_name string

    • email string

    • email_verified_at string

    • email_verification_excluded boolean

    • email_verification_sent_at string

    • phone string

    • phone_e164 string

    • phone_country string

    • phone_type string

    • has_whatsapp string

    • whatsapp_checked_at string

    • whatsapp_history_synced_until string

    • whatsapp_history_exhausted boolean

    • avatar_url string

    • additional_emails array

    • additional_phones array

    • company_name string

    • contact_type string

    • timezone string

    • source string

    • source_number string

    • date_of_birth string

    • dnd_all boolean

    • dnd_email boolean

    • dnd_sms boolean

    • dnd_calls boolean

    • dnd_voicemail boolean

    • dnd_gmb string

    • dnd_fb_messenger string

    • dnd_meta object

    • last_activity_at string

    • created_at string

    • updated_at string

    • owner_id string

    • lead_stage_id string

Delete a contact

Soft-deletes one contact by id: it stops appearing anywhere in the CRM and is recoverable. All associated activity, conversations, and tags remain intact for audit purposes.

DELETE /api/contacts/{id}

Path parameters

  • id integer required

    The contact ID.

    Example: 42

curl --request DELETE \
    "https://klozzo.com/api/contacts/42" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
{
    "message": "Contact deleted."
}
  • message string

Bulk action on contacts

Performs an action on multiple contacts in one call. Currently only delete is supported. Bulk delete is permanent (no soft-delete) and emits a single audit log entry covering all affected IDs.

POST /api/contacts/bulk

Body parameters

  • action string required

    Action to perform. Currently only delete.

    Example: delete

  • ids integer[] required

    IDs of contacts to act on (min 1).

    Example: [16]

curl --request POST \
    "https://klozzo.com/api/contacts/bulk" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"action\": \"delete\",
    \"ids\": [
        16
    ]
}"
{
    "message": "Bulk action completed."
}
  • message string

Check whether a contact's email really exists

Queues a deliverability check against the configured verification provider and answers 202 straight away — the result is not ready when this call returns. Read email_verification_status on the contact (or wait for the contact.updated webhook) to find out how it went.

POST /api/contacts/{contact_id}/verify-email

Use it before a first send to a list you did not collect yourself. A bounce rate above a few percent is what gets a sending domain blocked, and that is not something you undo in an afternoon.

The check costs money per address on most providers, so it is deliberately a call you make, never something the CRM does on its own.

Path parameters

  • contact_id string required

    The contact's id.

    Example: 42

Body parameters

  • driver string

    Force a specific provider instead of the account default. One of zerobounce, mailgun, ses, null.

    Example: zerobounce

curl --request POST \
    "https://klozzo.com/api/contacts/42/verify-email" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"driver\": \"zerobounce\"
}"
{
    "message": "Email verification queued.",
    "contact_id": 42,
    "status": "unverified"
}
  • message string

  • contact_id integer

  • status string

Reassign contact owner

Transfers ownership of a contact to a different user. Pass owner_id = null to unassign. Requires the reassignOwner policy.

PATCH /api/contacts/{contact_id}/owner

Path parameters

  • contact_id integer required

    The contact ID.

    Example: 42

Body parameters

  • owner_id integer

    User ID of the new owner. Set to null to unassign.

    Example: 7

curl --request PATCH \
    "https://klozzo.com/api/contacts/42/owner" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"owner_id\": 7
}"
{
    "data": {
        "id": null,
        "ulid": null,
        "first_name": "Audra",
        "last_name": "Crooks",
        "full_name": "Audra Crooks",
        "email": "rempel.chadrick@example.org",
        "email_verified_at": null,
        "email_verification_excluded": false,
        "email_verification_sent_at": null,
        "phone": "+12025550105",
        "phone_e164": null,
        "phone_country": "CO",
        "phone_type": null,
        "has_whatsapp": null,
        "whatsapp_checked_at": null,
        "whatsapp_history_synced_until": null,
        "whatsapp_history_exhausted": false,
        "avatar_url": null,
        "additional_emails": [],
        "additional_phones": [],
        "company_name": "Gaylord and Sons",
        "contact_type": "customer",
        "timezone": "America/Eirunepe",
        "source": "manual",
        "source_number": null,
        "date_of_birth": "1991-08-13",
        "dnd_all": false,
        "dnd_email": false,
        "dnd_sms": false,
        "dnd_calls": false,
        "dnd_voicemail": false,
        "dnd_gmb": null,
        "dnd_fb_messenger": null,
        "dnd_meta": [],
        "last_activity_at": null,
        "created_at": null,
        "updated_at": null,
        "owner_id": null,
        "lead_stage_id": null
    }
}
  • data object

    • id string

    • ulid string

    • first_name string

    • last_name string

    • full_name string

    • email string

    • email_verified_at string

    • email_verification_excluded boolean

    • email_verification_sent_at string

    • phone string

    • phone_e164 string

    • phone_country string

    • phone_type string

    • has_whatsapp string

    • whatsapp_checked_at string

    • whatsapp_history_synced_until string

    • whatsapp_history_exhausted boolean

    • avatar_url string

    • additional_emails array

    • additional_phones array

    • company_name string

    • contact_type string

    • timezone string

    • source string

    • source_number string

    • date_of_birth string

    • dnd_all boolean

    • dnd_email boolean

    • dnd_sms boolean

    • dnd_calls boolean

    • dnd_voicemail boolean

    • dnd_gmb string

    • dnd_fb_messenger string

    • dnd_meta object

    • last_activity_at string

    • created_at string

    • updated_at string

    • owner_id string

    • lead_stage_id string

Add a follower to a contact

Followers receive notifications about activity on the contact. Idempotent: adding an existing follower is a no-op.

POST /api/contacts/{contact_id}/followers

Path parameters

  • contact_id integer required

    The contact ID.

    Example: 42

Body parameters

  • user_id integer required

    ID of the user to add as follower.

    Example: 7

curl --request POST \
    "https://klozzo.com/api/contacts/42/followers" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"user_id\": 7
}"
{
    "message": "Follower added.",
    "followers": [
        7,
        12
    ]
}
  • message string

  • followers integer[]

Remove a follower from a contact

Stops sending this user notifications about the contact. It does not touch ownership: the owner is who the contact belongs to, a follower is only somebody watching, and removing the last follower leaves the owner intact.

DELETE /api/contacts/{contact_id}/followers/{user}

Idempotent — removing somebody who was not following answers 200 all the same, so a sync that cannot remember what it already did is safe.

Path parameters

  • contact_id integer required

    The contact ID.

    Example: 42

  • user integer required

    ID of the user to remove.

    Example: 7

curl --request DELETE \
    "https://klozzo.com/api/contacts/42/followers/7" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
{
    "message": "Follower removed.",
    "followers": [
        12
    ]
}
  • message string

  • followers integer[]

List a contact's do-not-disturb settings

Lists the do-not-disturb settings of one contact: one entry per channel this person has asked not to be reached on. An empty list means nothing is blocked — the absence of a row is the permission.

GET /api/contacts/{contact_id}/dnd-settings

is_active is the field to read: a setting can exist and be inactive because it was scheduled until a date that has already passed.

Path parameters

  • contact_id string required

    The contact's id.

    Example: 42

curl --request GET \
    --get "https://klozzo.com/api/contacts/42/dnd-settings" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
{
    "data": [
        {
            "id": 9,
            "contact_id": 42,
            "channel": "sms",
            "enabled": true,
            "scheduled_until": null,
            "set_by": 3,
            "set_at": "2026-08-23T17:04:00+00:00",
            "is_active": true
        }
    ]
}
  • data object[]

    • id integer

    • contact_id integer

    • channel string

    • enabled boolean

    • scheduled_until string

    • set_by integer

    • set_at string

    • is_active boolean

Block a channel for a contact

Records that this person does not want to be reached on one channel. The CRM checks it before every send, so blocking a channel here stops messages queued by anything — a sequence, an automation, an agent typing in the inbox.

POST /api/contacts/{contact_id}/dnd-settings

Sending the same channel twice updates the existing setting instead of creating a second one, so a retry is safe.

Pass scheduled_until for a temporary block ("not until after the holidays"); leave it out for an indefinite one. Every change is written to the audit log with who made it — consent decisions have to be provable.

Path parameters

  • contact_id string required

    The contact's id.

    Example: 42

Body parameters

  • channel string required

    Example: architecto

  • enabled boolean

    Example: true

  • scheduled_until string

    Must be a valid date. Must be a date after <code>now</code>.

    Example: 2052-09-18

curl --request POST \
    "https://klozzo.com/api/contacts/42/dnd-settings" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"channel\": \"architecto\",
    \"enabled\": true,
    \"scheduled_until\": \"2052-09-18\"
}"
{
    "data": {
        "id": 9,
        "contact_id": 42,
        "channel": "sms",
        "enabled": true,
        "scheduled_until": null,
        "set_by": 3,
        "set_at": "2026-08-23T17:04:00+00:00",
        "is_active": true
    }
}
  • data object

    • id integer

    • contact_id integer

    • channel string

    • enabled boolean

    • scheduled_until string

    • set_by integer

    • set_at string

    • is_active boolean

Unblock a channel for a contact

Removes the setting, which means the channel is open again. Use it when somebody opts back in — never to "clean up" a list, because a deleted block is a lost consent decision and the audit entry is the only trace left of it.

DELETE /api/contacts/{contact_id}/dnd-settings/{dndSetting_id}

A setting that belongs to another contact answers 404, even if the id exists.

Path parameters

  • contact_id string required

    The contact's id.

    Example: 42

  • dndSetting_id string required

    The setting's id, from the list endpoint.

    Example: 9

curl --request DELETE \
    "https://klozzo.com/api/contacts/42/dnd-settings/9" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
{
    "message": "DND setting removed."
}
  • message string

Read a contact's custom field values

Returns the custom field values of one contact as a flat object keyed by the field's key — the same keys you send when writing. Fields the contact has never been given a value for are simply absent; there is no null placeholder for them.

GET /api/contacts/{contact_id}/custom-field-values

To find out which keys exist in this account, call GET /api/contacts/properties (everything a contact can carry) or GET /api/custom-fields (the custom ones with their type and options).

Path parameters

  • contact_id string required

    The contact's id.

    Example: 42

curl --request GET \
    --get "https://klozzo.com/api/contacts/42/custom-field-values" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
{
    "data": {
        "presupuesto_mensual": "25000",
        "canal_preferido": "whatsapp"
    }
}
  • data object

    • presupuesto_mensual string

    • canal_preferido string

Write a contact's custom field values

Writes the custom field values of one contact. Send the values you want to set inside values, keyed by the field's key, uuid or numeric id. Only the keys you send are touched — this is a merge, not a replacement, so omitting a field leaves it alone and there is no way to accidentally blank the rest of the record.

PUT /api/contacts/{contact_id}/custom-field-values

Unlike POST /api/leads, an unknown key here is an error, not something kept aside: this endpoint exists to write values on purpose, so a typo is a bug worth stopping. You get a 422 naming every key that did not match, and nothing is written.

Values are validated against the field's own type — a date field rejects "mañana", a dropdown rejects an option that is not on its list.

Custom fields belong to the location, not to whoever created them: a field another agent added is yours to write too.

Path parameters

  • contact_id string required

    The contact's id.

    Example: 42

Body parameters

  • values object required

    The values to set, keyed by field.

    Example: {"presupuesto_mensual":"25000"}

curl --request PUT \
    "https://klozzo.com/api/contacts/42/custom-field-values" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"values\": {
        \"presupuesto_mensual\": \"25000\"
    }
}"
{
    "message": "Custom field values synced."
}
  • message string

List a contact's activity timeline

Lists the activity of one contact, newest first: pages visited, forms submitted, calls, notes, tag changes, messages. It is the same feed the contact screen shows.

GET /api/contacts/{contact_id}/activity

Paginated by cursor, not by page number. The timeline grows while you read it, and a page number would either repeat or skip events as new ones arrive. Follow next_cursor until it comes back null; do not build ?page=2 by hand.

Filter with event_type to sync one kind of thing — pass it more than once for several. The payload shape depends on the event type, so read event_type before reaching into payload.

Path parameters

  • contact_id string required

    The contact's id.

    Example: 42

Query parameters

  • per_page integer

    Rows per page. Default 25, maximum 100. See Lists, paging and filters.

    Example: 25

  • cursor string

    The next_cursor from the previous response.

    Example: eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0

  • event_type string[]

    Only these event types.

    Example: ["page_visited","form_submitted"]

curl --request GET \
    --get "https://klozzo.com/api/contacts/42/activity?per_page=25&cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0&event_type[]=page_visited&event_type[]=form_submitted" \
    --header "Authorization: Bearer {YOUR_API_TOKEN}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
{
    "data": [
        {
            "id": 881,
            "event_type": "form_submitted",
            "subject_type": "contact",
            "subject_id": 42,
            "actor_type": null,
            "actor_id": null,
            "location_id": 3,
            "payload": {
                "form_id": "newsletter",
                "utm_source": "adwords"
            },
            "occurred_at": "2026-08-23T17:04:00+00:00"
        }
    ],
    "next_cursor": "eyJpZCI6ODgxfQ",
    "prev_cursor": null
}
  • data object[]

    • id integer

    • event_type string

    • subject_type string

    • subject_id integer

    • actor_type string

    • actor_id string

    • location_id integer

    • payload object

      • form_id string

      • utm_source string

    • occurred_at string

  • next_cursor string

  • prev_cursor string