Help with NTLM Authentication and POST commands in 8.3

I need to make API calls to a system that will only allow authentication via NTLM in Ignition 8.3.
After being authenticated, I need to POST to two APIs as an authenticated user.
I’m writing a Project Library script that will be called via a Gateway Tag Change event.
The tag change will have access to parameter values that need to be sent in the POSTs.

I haven’t figured out how to set the headers or the body/params yet.

Here’s what I have so far:


""" 
This will call three APIs to step through the process of printing a Material Tag.
The material must have been previously accepted and must not be expired.
    1. login so the server can trust Search and Print requests
    2. query to get acceptance details
    3. update the JSON record with process info from the PLC and Ignition
    4. send the updated JSON to create quantity details and print a tag
    Note: Password changes are maintained in KeePass2 shared file
"""

# import libraries recommended by Ryan McLaughlin  

from org.apache.http.auth import AuthScope
from org.apache.http.auth import NTCredentials
from org.apache.http.client.methods import HttpGet
from org.apache.http.client.methods import HttpPost        # added because POST is required
from org.apache.http.impl.client import DefaultHttpClient
from org.apache.http.impl.client import BasicResponseHandler
from org.apache.http.entity import ContentType
from java.io import ByteArrayOutputStream

# initialize variables from tag values
tagPaths = [
    '[NG]Costpoint/LoginUser',
    '[NG]Costpoint/Search',
    '[NG]Costpoint/Print',
    '[NG]Costpoint/Database',
    '[NG]Costpoint/Domain',
    '[NG]Costpoint/Username',
    '[NG]Costpoint/Password'
]
tagValues = system.tag.readBlocking(tagPaths)

loginUrl = tagValues[0].value
searchUrl = tagValues[1].value
printUrl = tagValues[2].value
database = tagValues[3].value
domain = tagValues[4].value
username = tagValues[5].value
password = tagValues[6].value


def loginUser(moNumber, quantity, printer, labelCount):
    # Setup the client with NTLM Auth
    httpclient = DefaultHttpClient()
    
    # Define the credentials to use
    creds = NTCredentials(username, password, system.net.getHostName(), domain)
    print('creds={}'.format(creds))
    
    httpclient.getCredentialsProvider().setCredentials(AuthScope.ANY, creds)
    print('httpclient={}'.format(httpclient))
    
    # Define the host and URL to call
    httppost = HttpPost(loginUrl)
    print('httppost={}'.format(httppost))
    
    # Add headers
    header = {'Accept':'application/json'}
    try:
        httppost.addHeader(header)
    except:
        print('error trying to addHeader')
    
    # Execute the request
    response = httpclient.execute(httppost)
    print('response={}'.format(response))
    
    # Get response entity and MIME type
    entity = response.getEntity()
    print('entity={}'.format(entity))
    
    # Process the content
    contentType = ContentType.getOrDefault(response.getEntity()).getMimeType().lower()
    print('contentType={}'.format(contentType))
    
#    if 'json' in contentType:
#        return system.util.jsonDecode(BasicResponseHandler().handleResponse(response))
#    elif contentType in ["image/png", "image/jpeg"]:
#        baos = ByteArrayOutputStream()
#        entity.writeTo(baos)
#        return baos.toByteArray(), contentType
#    else:
#        return BasicResponseHandler().handleResponse(response), contentType
    
    httppost.releaseConnection()
    
    # step 2 search for the material tag
    httppost = HttpPost(searchUrl)
        
    searchParams = {'findBy':'MO','findText':moNumber,'findSerLot':'','database':database}
    try:
        httppost.setParams(searchParams)
    except:
        print('error trying to setParams')
    
    jsonResponse = httpclient.execute(httppost)
    print('jsonResponse={}'.format(jsonResponse))
    # step 3 update elements of the search results and print a material tag
    #jsonResponse['searchResults'][0]['quantity'] = quantity
    #jsonResponse['searchResults'][0]['labelCount'] = labelCount
    
    ###
    ###
    
    printPayload={'printer':printer,'userId':username,'userDisplayName':'Ignition','database':database,'labelRecords':jsonResponse}

This is on a parent project just printing via this script in the script console

moNumber = '33PM097734'
quantity = 19.4
printer = 'PRN060050'
labelCount = 1

AcceptTag.tagTest.loginUser(moNumber, quantity, printer, labelCount)

Printed output is:

creds=[principal: myDomain\myUsername][workstation: myWorkstation]
httpclient=org.apache.http.impl.client.DefaultHttpClient@4b841149
httppost=POST http://myURL/LoginUser HTTP/1.1
error trying to addHeader
response=HTTP/1.1 415 Unsupported Media Type [Transfer-Encoding: chunked, Server: Microsoft-IIS/10.0, Persistent-Auth: true, X-Powered-By: ASP.NET, Date: Wed, 26 Nov 2025 20:31:34 GMT] org.apache.http.conn.BasicManagedEntity@70e60d8c
entity=org.apache.http.conn.BasicManagedEntity@70e60d8c
contentType=text/plain
error trying to setParams
jsonResponse=HTTP/1.1 415 Unsupported Media Type [Transfer-Encoding: chunked, Server: Microsoft-IIS/10.0, X-Powered-By: ASP.NET, Date: Wed, 26 Nov 2025 20:31:34 GMT] org.apache.http.conn.BasicManagedEntity@59e15099

See AbstractHttpMessage (Apache HttpCore 4.4.13 API)

It doesn't accept a dictionary, it accepts either 2 string parameters or a Header object.

Be aware that gateway events will use the account that is running the service--you may need to change the service to run under a user with appropriate privileges.

The service account running the gateway is registered with the API service.
I have a working Python version that runs start-to-finish written in Visual Studio Code so I know the credentials are good.

The VSC version uses the from requests_ntlm import HttpNtlmAuth library.

Thanks @Kevin.Herron, the header problem is fixed.
Still get HTTP/1.1 415 Unsupported Media Type

Consider running wireshark while testing with the gateway and then testing with CPython. Compare.