API auth token error 'Invalid client credentials.'

Project Name (if applicable, otherwise just type n/a)

n/a

Question

I have two projects, one a new rework of the other, lets call them weeds-new and weed-original.

I have set up API and APPS for both ( I did new first - this may be relevant as it is the one that works!)

project weeds-new:

project	Tiritiri Matangi Island weed reports
slug	tiritiri-matangi-island-weed-reports
ref	1896047ff4244fe2b6c959e1d7f24867. 
Name	Client ID	Client Secret	Created At		
 SOTM_weeds	 5885	 <redacted>  04/01/2025 20:12

project weeds-original:

project	Tiriweeds
slug	tiriweeds
ref	d09ab480b37c4d6cbf41b804a5987567
Name	Client ID	Client Secret	Created At		
 SOTM_weeds_original	 5886	<redacted> 	04/01/2025 21:16

I note client is the same for both.

I am using pyepicollect:

CLIENT_ID = 5885

projects= {
    'Tiritiri Matangi Island weed reports' :
    { "name": 'Tiritiri Matangi Island weed reports',
            "slug" : 'tiritiri-matangi-island-weed-reports',
           'secret': '<redacted>'
           },
    'Tiriweeds' : { "name": 'Tiriweeds',
            "slug" : 'tiriweeds' ,
            'secret': '<redacted>'
            }
}

conf = projects['Tiriweeds']  # fails
#conf = projects['Tiritiri Matangi Island weed reports'] # works
print(f"project: { conf['slug']}")

result = pyep.api.search_project(conf['name'])
pprint.pp( result )

token = pyep.auth.request_token(CLIENT_ID, conf['secret'])
if not token.get('access_token'):
    pp.pprint(token)
    sys.exit(1)
    
project = pyep.api.get_project(conf['slug'], token['access_token'])

entries = pyep.api.get_entries(conf['slug'], token['access_token'])
print(f"number of entries {len(entries['data']['entries'])}")

running the script for the two projects gives:

IT412392:pytest rful011$ python3 bin/test.1.py 
project: tiritiri-matangi-island-weed-reports
{'data': [{'type': 'project',
           'id': '1896047ff4244fe2b6c959e1d7f24867',
           'project': {'name': 'Tiritiri Matangi Island weed reports',
                       'slug': 'tiritiri-matangi-island-weed-reports',
                       'access': 'private',
                       'ref': '1896047ff4244fe2b6c959e1d7f24867'}}]}
number of entries 1
IT412392:pytest rful011$ python3 bin/test.1.py 
project: tiriweeds
{'data': [{'type': 'project',
           'id': 'd09ab480b37c4d6cbf41b804a5987567',
           'project': {'name': 'Tiriweeds',
                       'slug': 'tiriweeds',
                       'access': 'private',
                       'ref': 'd09ab480b37c4d6cbf41b804a5987567'}}]}
{ 'errors': [ { 'code': 'ec5_253',
                'source': 'token issue',
                'title': 'Invalid client credentials.'}]}

I have checked and rechecked the secret values…

update: I tried deleting and recreating both APP entries and now neither work. Sigh… At least that is consistent!
Any one know what is going on ?

Our demo project EC5 API Private is functioning as expected. For your reference, you can view the working example in the JSFiddle below:
👉 Demo Project - EC5 API Private

Since we are not the maintainers of the pyepicollect library, you might find it more effective to raise your question directly with the library’s developers:
👉 Pyepicollect Repository

That said, we suggest logging the details of the request sent to the token endpoint to ensure it aligns with the Epicollect5 API requirements. Specifically, you can focus on this function call:

pyep.auth.request_token(CLIENT_ID, conf['secret'])

Steps to Debug the Request

  1. Log the Request Content:
    Log the request payload and ensure it meets the specifications outlined in the Epicollect5 documentation:
    👉 Epicollect5 API Authentication Guide
  2. Modify the request_token Function Locally:
    If feasible, you can add logging to the request_token function in your local environment. This will help you verify the exact data being sent to the endpoint.
  3. Use a Proxy for Interception:
    Alternatively, tools like Postman or a proxy (e.g., Fiddler or Charles) can intercept and inspect the request for discrepancies.

Example: Enhanced request_token Function with Logging

Here’s a sample implementation that includes debugging details:

import requests
import pprint
import logging

# Configure logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

TOKEN_REQ_URL = "https://five.epicollect.net/api/oauth/token" 

def request_token(client_id, client_secret):
    """ Get token. Each token is valid for 2 hours

    :param client_id: The client ID for authentication
    :param client_secret: The client secret for authentication
    :return: A dictionary with the token information or error details
    """
    data = {
        'grant_type': 'client_credentials',
        'client_id': client_id,
        'client_secret': client_secret
    }

    logger.debug("Request URL: %s", TOKEN_REQ_URL)
    logger.debug("Request data: %s", pprint.pformat(data))

    response = requests.post(TOKEN_REQ_URL, data=data)

    if response.status_code == 200:
        return response.json()
    else:
        logger.error("Failed to obtain token. Response details: %s", pprint.pformat(response.json()))
        return {'error': 'Failed to obtain token', 'status_code': response.status_code, 'response': response.text}

# Example usage
CLIENT_ID = 12345  # Replace with your actual client ID
CLIENT_SECRET = '<redacted>'  # Replace with your actual secret

token = request_token(CLIENT_ID, CLIENT_SECRET)
print(token)

This code adds detailed logging to help pinpoint potential issues with the request payload or API endpoint. By implementing this, you can verify if the request conforms to the API’s requirements.

Thanks very much for your very helpful (and complete) answer.

I had already decided to try doing this using the raw api so I could get more debugging data so I used yours and cut and pasted the app details straight from the web browser. Surprise it worked.

Long story short: I had mistakenly though that the client_id was tied to the project owner and that it was the same for the two projects. ( there was one digit different and I am highly non visual so I did not notice). It took a another hours stuffing around before I finally worked out it must be the client_ids.

Usual story: problem between chair and the keyboard : )

Apologies for the noise!

1 Like