REST API to accept an incoming PDF - is it possible?

eddy42
Tera Expert

I need to write a REST API to accept an incoming PDF (Employee contract) from another system which generates it.  I want to attach it to the user record in ServiceNow.

 

The external system can easily call a given REST endpoint with a POST request and can also send the employee number as a parameter.

 

I have tried (unsuccessfully) to read the binary file sent using Postman (as binary data) - I can create the attachment, give it a name and relate it to the relevant record by doing a lookup and setting the record on the attachment, but the data is corrupt or I am reading the wrong values.

 

I have spoken to ServiceNow Support and they tell me that this is not supported and impossible - If that's the case I am extremely surprised - but I'll have to accept it - so I throw that challenge to the community.

 

Note: I cannot use the out of the box attachment API because the external system doesn't (and shouldn't) know the table name and sys_id - it only knows the employee number - I want a custom endpoint that handles this gracefully and confirms back success when the record is found and the attachment created.

Here is an example of code I've tried in the REST API:

 

var grUser = new GlideRecord('sys_user');
grUser.get('employee_number', params.ref); //This is from the REST parameters

var attachment = new GlideSysAttachment();
//set up inputs
var fileName = 'sample';
var contentType = 'application/pdf';
var agr = attachment.write(grUser, fileName+'.pdf', contentType, request.body.dataStream);

 I think the issue is with the last line - request.body.dataStream - it doesn't appear to hold the actual data.

 

Does anyone know of a way to do this?  I really don't want to have to give an external system access to write attachments with the attachments API as it may do so in an uncontrolled way.

 

Note: This also has to be possible in scope as it's a scoped application.

1 ACCEPTED SOLUTION

Community Alums
Not applicable

Hi eddy,

 

I just did up a quick proof of concept that accepts an employee number as a path parameter along with a JSON body that contains a file name, content type and base64 encoded string of the file contents.

The scripted REST api then uses the GlideSysAttachment API's writeBase64() method (note I believe this is only available in scoped applications) for global scope you would need to decode the base64 string first to create the attachment against the target record.

 

I agree with Tony's comments around base64 payloads though and also considering having your Scripted REST API or Table API return the target record details and then have the source system use the Attachment API with those details.

 

Here's a basic example:

 

 

(function process( /*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {

    try {
        var responseObj = {};
        var errorMessages = [];
	    var fileName = request.body.data.fileName; 
	    var contentType = request.body.data.contentType;
        var base64EncodedContent = request.body.data.content;
        var employeeNumber = request.pathParams.employee_number;

        var attachment = new GlideSysAttachment();
        var employeeGr = new GlideRecord('sys_user');
        if (employeeGr.get('employee_number', employeeNumber)) {
		    var attachmentGr = attachment.writeBase64(employeeGr, fileName, contentType, base64EncodedContent);
	    } else throw 'Unable to find employee with employee number of ' + employeeNumber;

	    responseObj.employeeName = employeeGr.getValue('name');
        responseObj.employeeNumber = employeeNumber;
	    responseObj.employee_sys_id = employeeGr.getUniqueValue();

        response.setBody(responseObj);
        response.setStatus(200);

    } catch (err) {
        response.setStatus(500);
        errorMessages.push(err);
        responseObj.errorMessages = errorMessages;
        response.setBody(responseObj);
    }
})(request, response);

 

 

 

The Request Body would look something like:

JoelMillwood_0-1688606780274.png

 

And the result:

JoelMillwood_0-1688607342532.png

 

 

 

 

 

View solution in original post

12 REPLIES 12

ersureshbe
Giga Sage
Giga Sage

Hi, You can achieve through attachment API using binary format

 

Another option you should use ECC queue . pls refer below article.

https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB0546294

 

Regards,
Suresh.

Thanks ersureshbe - but how do I use either of those APIs if the system sending the data doesn't know the table name or sys_id of the record.

I need the external system to send to a custom API I create where it can send the attachment, employee number and date of original creation of the contract.  ServiceNow needs to accept the request, check the user record exists and that the supplied data is valid and then send a success message indicating the contract has been accepted.
Neither the attachment or the ecc_queue can do this.

Instead I am going to have to create a service in the middle that can accept the REST post, then make a GET request to ServiceNow to query the details, then create a POST request to the attachment API.  What software do I use to make this internal REST service, why can't ServiceNow handle the whole thing?

Community Alums
Not applicable

Hi eddy42,

 

You can create your own API using the Scripted REST API functionality in ServiceNow. Your API can accept the parameters you mentioned (employee number, date of original creation of the contract etc) and can perform the requisite checks you mentioned as well.

 

Your API could also act as a wrapper for the Attachment API and use the Attachment API to attach the PDF to the user record as required. This way the source system does not need to know the target table nor sys_id.

 

Please review the following product documentation for additional information: Scripted REST APIs (servicenow.com).

 

The Developer site also has a good learning course on Inbound Integrations and the Scripted REST API capabilities which you can find here: Scripted REST API Objectives | ServiceNow Developer.

I'd be very interested to know (as per my code sample) how I can pass the incoming binary data to the servicenow attachment API) - ServiceNow say this is not possible.  The guides you have sent are just generic and as you can see I have already created a scripted REST API - but that API CANNOT accept any incoming binary data.

So far the closest I think might be an option is to convert the incoming binary file to text (base64) send that in as a string and then decode it internally in ServiceNow.

Community Alums
Not applicable

Hi eddy,

 

I just did up a quick proof of concept that accepts an employee number as a path parameter along with a JSON body that contains a file name, content type and base64 encoded string of the file contents.

The scripted REST api then uses the GlideSysAttachment API's writeBase64() method (note I believe this is only available in scoped applications) for global scope you would need to decode the base64 string first to create the attachment against the target record.

 

I agree with Tony's comments around base64 payloads though and also considering having your Scripted REST API or Table API return the target record details and then have the source system use the Attachment API with those details.

 

Here's a basic example:

 

 

(function process( /*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {

    try {
        var responseObj = {};
        var errorMessages = [];
	    var fileName = request.body.data.fileName; 
	    var contentType = request.body.data.contentType;
        var base64EncodedContent = request.body.data.content;
        var employeeNumber = request.pathParams.employee_number;

        var attachment = new GlideSysAttachment();
        var employeeGr = new GlideRecord('sys_user');
        if (employeeGr.get('employee_number', employeeNumber)) {
		    var attachmentGr = attachment.writeBase64(employeeGr, fileName, contentType, base64EncodedContent);
	    } else throw 'Unable to find employee with employee number of ' + employeeNumber;

	    responseObj.employeeName = employeeGr.getValue('name');
        responseObj.employeeNumber = employeeNumber;
	    responseObj.employee_sys_id = employeeGr.getUniqueValue();

        response.setBody(responseObj);
        response.setStatus(200);

    } catch (err) {
        response.setStatus(500);
        errorMessages.push(err);
        responseObj.errorMessages = errorMessages;
        response.setBody(responseObj);
    }
})(request, response);

 

 

 

The Request Body would look something like:

JoelMillwood_0-1688606780274.png

 

And the result:

JoelMillwood_0-1688607342532.png