Custom service returns "401 (Unauthorized : Missing required argument merchant_id)"
You have to use the POST method in the sync_contacts_get_customers_list function:

options.method = 'POST';

Without this, you aren't "posting" anything up to Drupal Services, so your resource in Drupal will not be delivered the merchant_id.

If you want to use a GET request, then I'd recommend using a Views JSON (from Views datasource) and a contextual filter(s), otherwise custom resources in Services always use POST AFAIK, unless you're building a CRUD interface for an Entity (e.g. Services Entity API).

Also the way you are defining your custom resource in Drupal is incorrect, use actions instead:

  $resources = array(
    'sync_contacts_resources' =&gt; array(
      'actions' =&gt; array(
        'get_contacts' =&gt; array(
          'help' =&gt; 'Get customers and their contacts',
          'file' =&gt; array('type' =&gt; 'inc', 'module' =&gt; 'sync_contacts', 'name' =&gt; 'resources/sync_contacts.resource'),
          'callback' =&gt; '_get_customer_contacts',
          'args' =&gt; array(
            array(
              'name' =&gt; 'merchant_id',
              'type' =&gt; 'int',
              'description' =&gt; t('Get customers and their contacts by merchant_id.'),
              'source' =&gt; array('data' =&gt; 'merchant_id'),
              'optional' =&gt; FALSE,              
            ),
          ),
          'access callback file' =&gt; array(
            'type' =&gt; 'inc',
            'module' =&gt; 'drupalgap',
            'name' =&gt; 'drupalgap.resource',
          ),
          'access callback' =&gt; '_sync_contacts_access',
          'access arguments' =&gt; array('view'),
          'access arguments append' =&gt; TRUE,
        ),
      ),
    ),
  );