- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
07-04-2023 09:39 AM
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.
Solved! Go to Solution.
- Labels:
-
API
-
Integrations
-
REST
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
07-05-2023 06:33 PM - edited 07-05-2023 08:48 PM
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:
And the result:
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
07-07-2023 03:26 AM
Thanks - this is the most sensible solution I've seen so far. I will certainly try this.
The key bit of information here is that you've used request.body.data.content - I was using request.body.dataStream as per the ServiceNow docs and maybe this is where I was going wrong.
Much appreciated - and the amount of work documenting your solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
07-07-2023 03:35 AM
Thanks again @Community Alums for this - I'm going to accept it as the solution because it's about as close as I'm going to get.
I understand the issues around base64 as stated but for relatively small files it would be an option. I already have a 'solution' doing a get request first but that requires either the 3rd party building something, or me building something in the middle to mask the need for two requests.
Once again, it's a shame ServiceNow cannot allow import of a binary file in a custom REST API (requiring we use their attachment API) - this is really a workaround, but the best option in the absence of that functionality.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
07-09-2023 05:32 PM
Your welcome @eddy42! Agreed regarding your comments. This workaround is all I have come up with for now; if time permits I will revisit and if I come up with a better solution, I'll let you know.
Kind regards,
Joel.