Skip to content

findContactWithName

It is not uncommon that when logging a call, a contact cannot be found using the findContact interface which attempts to lookup a contact into the target CRM via a phone number. Sometimes however, a contact cannot be found, but is in fact known to be in the CRM. This is when this interface comes into play.

Search contacts in a CRM

Searching contacts in a CRM via App Connect

When a contact cannot be found, users are given the option to search the CRM for a contact by name. This allows users to associate calls with contacts in a more manual fashion when a contact is not found via a phone number.

This interface is called to facilitate that search process.

Request parameters

Parameter Description
user An object describing the Chrome extension user associated with the action that triggered this interface.
authHeader The HTTP Authorization header to be transmitted with the API request to the target CRM.
name The text entered by the user that should be searched for within the CRM.

Return value(s)

This interface returns a single object. That object describes the contacts that were found. It has following properties:

Parameter Description
successful True or false is a contact was found or not.
matchedContactInfo An array of objects containing id, name and optionally additionalInfo and isNewContact.
extraDataTracking Information relating to rate limited, if applicable.

Example

{
  successful: true,
  matchedContactInfo:[
    {
      id: 'contact id',
      name: 'John Doe',
      phone: '(123) 456-7890',
      type: 'Lead',
      additionalInfo: null,
      isNewContact: false
    }
  ],
  extraDataTracking: {
    ratelimitRemaining: null,
    ratelimitAmount: null,
    ratelimitReset: null
  }
}

Reference

async function findContactWithName({ user, authHeader, name }) {
    const matchedContactInfo = [];
    let extraDataTracking = {};
    /*
    Clio's contact search functionality works correctly with name-based queries, including first name, last name, and full name. 
    It handles all variations without requiring the query to be split
    */
    const personInfo = await axios.get(`https://${user.hostname}/api/v4/contacts.json?type=Person&query=${name}&fields=id,name,title,company,primary_phone_number`, {
        headers: { 'Authorization': authHeader }
    });
    extraDataTracking = {
        ratelimitRemaining: personInfo.headers['x-ratelimit-remaining'],
        ratelimitAmount: personInfo.headers['x-ratelimit-limit'],
        ratelimitReset: personInfo.headers['x-ratelimit-reset']
    };
    if (personInfo.data.data.length > 0) {
        for (var result of personInfo.data.data) {
            const matterInfo = await axios.get(
                `https://${user.hostname}/api/v4/matters.json?client_id=${result.id}&fields=id,display_number,description,status`,
                {
                    headers: { 'Authorization': authHeader }
                });
            let matters = matterInfo.data.data.length > 0 ? matterInfo.data.data.map(m => { return { const: m.id, title: m.display_number, description: m.description, status: m.status } }) : null;
            matters = matters?.filter(m => m.status !== 'Closed');
            let associatedMatterInfo = await axios.get(
                `https://${user.hostname}/api/v4/relationships.json?contact_id=${result.id}&fields=matter{id,display_number,description,status}`,
                {
                    headers: { 'Authorization': authHeader }
                });
            extraDataTracking = {
                ratelimitRemaining: associatedMatterInfo.headers['x-ratelimit-remaining'],
                ratelimitAmount: associatedMatterInfo.headers['x-ratelimit-limit'],
                ratelimitReset: associatedMatterInfo.headers['x-ratelimit-reset']
            };
            let associatedMatters = associatedMatterInfo.data.data.length > 0 ? associatedMatterInfo.data.data.map(m => { return { const: m.matter.id, title: m.matter.display_number, description: m.matter.description, status: m.matter.status } }) : null;
            associatedMatters = associatedMatters?.filter(m => m.status !== 'Closed');
            let returnedMatters = [];
            returnedMatters = returnedMatters.concat(matters ?? []);
            returnedMatters = returnedMatters.concat(associatedMatters ?? []);
            matchedContactInfo.push({
                id: result.id,
                name: result.name,
                title: result.title ?? "",
                type: 'contact',
                company: result.company?.name ?? "",
                phone: result.primary_phone_number ?? "",
                additionalInfo: returnedMatters.length > 0 ?
                    {
                        matters: returnedMatters,
                        logTimeEntry: user.userSettings?.clioDefaultTimeEntryTick ?? true,
                        nonBillable: user.userSettings?.clioDefaultNonBillableTick ?? false
                    } :
                    {
                        logTimeEntry: user.userSettings?.clioDefaultTimeEntryTick ?? true
                    }
            })
        }
    }

    return {
        successful: true,
        matchedContactInfo,
        extraDataTracking
    }
}