- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
on ‎11-03-2021 02:04 AM
I've successfully submitted a multi-part Rest API, so I'll post an example Script.
This Script is sent using an attachment as a multi-part body.
Attachments are multi-part data that includes text and binary data. Examples include JSON and PDF.
I use "ByteArrayOutputStream" and "GlideSysAttachment.write" to create attachments.
It does not use "MultipartHelper". It only works with the example Script.
To execute the example Script, create the following Script in sys_script_fix (or Script include), rewrite the SYSID to it. (attachmentTableSysid)
Next, rewrite the SYSID of the PDF to be sent. (pdfAttachmentSysid)
Please rewrite and execute according to your instance.
Thank you.
--- Note ---
I've used FlowDesigner's multipart and have failed to create complex transmissions containing text and binaries. So I used the example Script to send the multipart as a single attachment and it finally succeeded.
// Example RestAPI Multi part body
var bodyList = [
// Please set the characters and data you want to send
'--xXxxxxxxxxxxxxxxxxXx',
'Content-Type: application/json; charset=UTF-8',
'Content-Disposition: form-data; name="metadata"',
'',
// Example JSON
JSON.stringify({
"name": "ExamplePDF.pdf",
"mimeType": "application/pdf",
"parents": "folder_id"
}),
'',
'--xXxxxxxxxxxxxxxxxxXx',
'Content-Type: application/pdf',
'Content-Disposition: form-data; name="file"',
'',
// Example PDF Binary Data
function(byteOutStrm) {
var pdfAttachmentSysid = 'ae95a9522f233010828ad6c6f699b638';
var attaInStrm = new GlideSysAttachmentInputStream(pdfAttachmentSysid);
attaInStrm.writeTo(byteOutStrm, 0, 0);
},
'',
'--xXxxxxxxxxxxxxxxxxXx--'
];
// Create a byte array output stream
var byteOutStrm = new Packages.java.io.ByteArrayOutputStream();
for (var i = 0; i < bodyList.length; i++) {
if (typeof bodyList[i] != 'function') {
var bytes = Packages.java.lang.String('' + bodyList[i] + '\n').getBytes();
byteOutStrm.write(bytes, 0, bytes.length);
} else {
bodyList[i].call(this, byteOutStrm);
}
}
// Create an attachment from a byte array
var attachment = new GlideSysAttachment();
// Table for storing work data. (any)
var attachmentTable = 'sys_script_fix';
var attachmentTableSysid = 'd07269162f233010828ad6c6f699b6bd';
var grAttachment = new GlideRecord(attachmentTable);
grAttachment.get(attachmentTableSysid);
var byteArray = byteOutStrm.toByteArray();
/*
# GlideSysAttachment.write
# Create an attachment.
# (GlideRecord, File Name, Content Type, Data)
*/
var attachmentSysID = attachment.write(grAttachment, 'PostBody', 'application/octet-stream', byteArray);
var attachmentLength = byteArray.length;
byteArray = null;
// Example RestAPI Multi part
if (attachmentSysID) {
var request = new sn_ws.RESTMessageV2();
request.setRequestHeader("Accept", "Application/json");
request.setRequestHeader("Content-Type", 'multipart/related; boundary="xXxxxxxxxxxxxxxxxxXx"');
request.setRequestHeader("Content-length", '' + attachmentLength);
var authId = 'AccountID';
var authPassword = 'Password';
request.setBasicAuth(authId, authPassword);
request.setHttpMethod('post');
// simple HTTP Request & Response Service. (https://httpbin.org)
request.setEndpoint('https://httpbin.org/post');
// Set Request Body From Attachment
request.setRequestBodyFromAttachment(attachmentSysID);
// Save Response Body As Attachment
request.saveResponseBodyAsAttachment(attachmentTable, attachmentTableSysid, 'ResponseJSON.txt');
// Sending a request
var response = request.execute();
var statusCode = response.getStatusCode();
gs.info('statusCode ' + statusCode);
}
- 10,929 Views
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hi @iwai! I just wanted to let you know that this script works great and was a big help for us. We were setting up an integration towards TopDesk, which required attachments to be sent using multi-part. Your post had all the required parts nicely added together as a working example. Nicely done!
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
It's nearly 2025 and we're still relying on scripts like this..
@iwai Thank you!
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hi, I have tons of headers, instead of sending in headers section can I send all the headers in multipart section where provide the body as well?
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
// Refactored on 2024-12-04: ServiceNow script for uploading a file to Google Drive
/**
* Note: The `GoogleCloudAPI_AUTH()` function is a proprietary, non-public implementation.
* It is used internally for obtaining Google Cloud API tokens.
*/
/**
* Function to obtain Google Drive API access token
* @returns {string} Access token
* @throws {Error} Throws an error if access token retrieval fails
*/
function getAccessToken() {
var responseBody = new global.GoogleCloudAPI_AUTH().requestToken(
'******', // Service account
'******', // Google user account
'******', // Required scopes
'******' // Token ID
);
var responseJson = JSON.parse(responseBody);
if (responseJson['access_token']) {
return responseJson['access_token'];
}
throw new Error('Failed to retrieve access token');
}
/**
* Function to upload a ServiceNow attachment to Google Drive
* {string} sysAttachmentSysId - sys_id of the ServiceNow attachment
* {string} folderId - Google Drive folder ID for the upload destination
* @returns {Object} Response from the Google Drive API after upload
* @throws {Error} Throws an error if the attachment is not found or the API request fails
*/
function uploadFileToGoogleDrive(sysAttachmentSysId, folderId) {
var accessToken = getAccessToken();
// Retrieve attachment information from ServiceNow
var grAttachment = new GlideRecord('sys_attachment');
if (!grAttachment.get(sysAttachmentSysId)) {
throw new Error('Specified attachment does not exist');
}
// Get the file name and MIME type
var fileName = grAttachment.getValue('file_name');
var contentType = grAttachment.getValue('content_type');
// Construct the multipart request body for Google Drive
var bodyList = [
'--xXxxxxxxxxxxxxxxxxXx',
'Content-Type: application/json; charset=UTF-8',
'',
JSON.stringify({
name: fileName, // File name
mimeType: contentType, // MIME type
parents: [folderId], // Google Drive folder ID for upload destination
}),
'',
'--xXxxxxxxxxxxxxxxxxXx',
'Content-Type: ' + contentType,
'',
function (byteOutStr) {
var attachmentStream = new GlideSysAttachmentInputStream(sysAttachmentSysId);
attachmentStream.writeTo(byteOutStr, 0, 0);
},
'',
'--xXxxxxxxxxxxxxxxxxXx--',
];
// Build the binary data
var byteOutStr = new Packages.java.io.ByteArrayOutputStream();
for (var i = 0; i < bodyList.length; i++) {
if (typeof bodyList[i] !== 'function') {
var bytes = Packages.java.lang.String(bodyList[i] + '\r\n').getBytes();
byteOutStr.write(bytes, 0, bytes.length);
} else {
bodyList[i].call(this, byteOutStr);
}
}
// Create a temporary record and attachment
var attachment = new GlideSysAttachment();
var tempRecord = new GlideRecord('sys_poll');
tempRecord.initialize();
tempRecord.message = 'Temporary attachments for file transfers';
tempRecord.insert();
var attachmentSysID = attachment.write(
tempRecord,
'PostBody',
'application/octet-stream',
byteOutStr.toByteArray()
);
byteOutStr.close();
// Send the request to Google Drive API
var request = new sn_ws.RESTMessageV2();
request.setRequestHeader('Accept', 'Application/json');
request.setRequestHeader('Content-Type', 'multipart/related; boundary="xXxxxxxxxxxxxxxxxxXx"');
request.setRequestHeader('Authorization', 'Bearer ' + accessToken);
request.setHttpMethod('POST');
request.setEndpoint('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true');
request.setRequestBodyFromAttachment(attachmentSysID);
var response = request.execute();
var statusCode = response.getStatusCode();
var responseBody = JSON.parse(response.getBody());
// Remove temporary attachment and record after upload
try {
attachment.deleteAttachment(attachmentSysID); // Delete temporary attachment
tempRecord.deleteRecord(); // Delete temporary record
gs.info('Temporary attachment and record successfully removed');
} catch (e) {
gs.error('Error while deleting temporary files: ' + e.message);
}
// Log the upload result
gs.info('Google Drive upload completed - Status Code: ' + statusCode);
gs.info('Response: ' + JSON.stringify(responseBody, null, 2));
return responseBody; // Return the upload result if needed
}
// Example usage
try {
var result = uploadFileToGoogleDrive('******', '******');
gs.info('Upload result: ' + JSON.stringify(result, null, 2));
} catch (e) {
gs.error('Error: ' + e.message);
}
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
@iwai Thanks for the refactoring and original script. I am trying to send the attachment file from ServiceNow to 'Manage engine Service Desk Plus system' through outbound REST API and it is expecting 'input_file' parameter with attachment (refer the screenshot from Postman). How can I accommodate 'input_file' parameter in body ? Help is appreciated.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
@SandyNow , There are two possible ways to include the input_file parameter in the request body, depending on how the API interprets the input_file value. Below are both versions:
- Version 1: Directly Reproducing Key and Value from Postman
If we strictly follow the Key (input_file) and Value (image10.jpg) as described in your Postman setup, the request body would look like this:
var boundary = 'xXxxxxxxxxxxxxxxxxXx';
var bodyList = [
'--' + boundary,
'Content-Disposition: form-data; name="input_file"', // Key: input_file
'',
'image10.jpg', // Value: image10.jpg
'',
'--' + boundary + '--',
];
- Strictly matches the Key and Value from Postman
- This sends "image10.jpg" as a text value, rather than the actual file
- Use this if the API only expects a file name reference, not an actual file upload
-----------------
- Version 2: Standard File Upload Format (Common REST API Implementation)
If the API expects an actual file upload, the request should include filename and file content, following standard multipart/form-data conventions:
var boundary = 'xXxxxxxxxxxxxxxxxxXx';
var bodyList = [
'--' + boundary,
'Content-Disposition: form-data; name="input_file"; filename="image10.jpg"', // Key: input_file, Value: actual file
'Content-Type: application/octet-stream', // Generic MIME type for binary files
'',
function (byteOutStr) {
var attachmentStream = new GlideSysAttachmentInputStream(sysAttachmentSysId);
attachmentStream.writeTo(byteOutStr, 0, 0);
},
'',
'--' + boundary + '--',
];
- Includes filename="image10.jpg" so the API recognizes the file
- Includes Content-Type to ensure correct file processing
- Sends the actual file content from ServiceNow
- Use this if the API expects an actual file to be uploaded
-----------------
Final Recommendation
- If the API only needs a file name reference, Version 1 is correct.
- If the API expects a real file upload, Version 2 should be used.
- If unsure, test both versions to see how the API responds.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
@iwai Thanks for your response. I have written a BR on Attachment Table and below is my current script (Note : It's in Scoped App) so where can I add you code as I was using ''setRequestBodyFromAttachment()" method.
var resultGr = new GlideRecord("sn_customerservice_case");
resultGr.addQuery("sys_id", current.table_sys_id);
resultGr.addQuery("account", '<account sys id>'); // running for specific account
resultGr.query();
if (resultGr.next()) {
var caseid = resultGr.sys_id.toString();
gs.info("Case sys id : " + caseid);
try {
var r = new sn_ws.RESTMessageV2('New Integration', 'UploadAttachment');
r.setRequestBodyFromAttachment(current.sys_id);
r.setRequestHeader("Content-Type", resultGr.content_type);
r.setStringParameterNoEscape('request_id', resultGr.correlation_id);
var response = r.execute();
var responseBody = response.getBody();
var httpStatus = response.getStatusCode();
gs.info("The value of the POST Response is " + httpStatus + "Body : " + responseBody);
} catch (ex) {
var message = ex.message;
}
}
Appreciate your help..
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
@iwai Hello, I'll mostly go with your 2nd recommendation but unsure about the code you suggested to place it in a correct place. Appreciate your response.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
@SandyNow , To adapt the function for your scenario where the Attachment Table Business Rule is handling the process, and current represents the attachment record, the following changes should be made:
Changes to uploadFileToGoogleDrive (Renamed for ManageEngine)
- Remove the explicit retrieval of the attachment record (var grAttachment = new GlideRecord('sys_attachment'))
- Since current already represents the attachment in the Business Rule context, grAttachment = current; should be used instead.
- This eliminates the need for sysAttachmentSysId as a function parameter.
- Modify references to grAttachment to use current directly
- fileName = current.file_name;
- contentType = current.content_type;
Corrected Code Section
// No need to retrieve sys_attachment separately, use current directly
var grAttachment = current; // current represents the attachment record in the Business Rule
// Get the file name and MIME type from the current attachment record
var fileName = grAttachment.getValue('file_name');
var contentType = grAttachment.getValue('content_type');
// Construct the multipart request body for ManageEngine ServiceDesk Plus
var bodyList = [
'--xXxxxxxxxxxxxxxxxxXx',
'Content-Disposition: form-data; name="input_file"; filename="' + fileName + '"',
'Content-Type: ' + contentType,
'',
function (byteOutStr) {
var attachmentStream = new GlideSysAttachmentInputStream(current.sys_id);
attachmentStream.writeTo(byteOutStr, 0, 0);
},
'',
'--xXxxxxxxxxxxxxxxxxXx--',
];
These changes ensure the function works within the Attachment Table Business Rule context without redundant queries.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
@iwai Thanks. I have modified the code as per your suggestion. But I am getting below error : Evaluator.evaluateString() problem: java.lang.SecurityException: Use of Packages calls is not permitted in scoped applications: com.glide.script.PackageScope.get(PackageScope.java:39)
Are you aware about the replacement of Java packages call in scoped app OR any other way to achieve this ?
This BR is written in Customer Service Scope.
(function executeRule(current, previous /*null when async*/ ) {
// Add your code here
var grAttachment = current; // current represents the attachment record in the Business Rule
// Get the file name and MIME type from the current attachment record
var fileName = grAttachment.getValue('file_name');
var contentType = grAttachment.getValue('content_type');
// Construct the multipart request body for ManageEngine ServiceDesk Plus
var bodyList = [
'--xXxxxxxxxxxxxxxxxxXx',
'Content-Disposition: form-data; name="input_file"; filename="' + fileName + '"',
'Content-Type: ' + contentType,
'',
function(byteOutStr) {
var attachmentStream = new GlideSysAttachmentInputStream(current.sys_id);
attachmentStream.writeTo(byteOutStr, 0, 0);
},
'',
'--xXxxxxxxxxxxxxxxxxXx--',
];
var byteOutStr = new Packages.java.io.ByteArrayOutputStream();
for (var i = 0; i < bodyList.length; i++) {
if (typeof bodyList[i] !== 'function') {
var bytes = Packages.java.lang.String(bodyList[i] + '\r\n').getBytes();
byteOutStr.write(bytes, 0, bytes.length);
} else {
bodyList[i].call(this, byteOutStr);
}
}
// Create a temporary record and attachment
var attachment = new GlideSysAttachment();
var tempRecord = new GlideRecord('sys_poll');
tempRecord.initialize();
tempRecord.message = 'Temporary attachments for file transfers';
tempRecord.insert();
var attachmentSysID = attachment.write(tempRecord, 'PostBody', 'application/octet-stream', byteOutStr.toByteArray());
byteOutStr.close();
var resultGr = new GlideRecord("sn_customerservice_case");
resultGr.addQuery("sys_id", current.table_sys_id);
resultGr.addQuery("account", 'f67195a7db72fd1036fbb6e4e2961955'); // specific account
resultGr.query();
if (resultGr.next()) {
var fileid = resultGr.sys_id.toString();
gs.info("Case sys id : " + fileid);
// Send the request
var request = new sn_ws.RESTMessageV2('SDP Integration', 'UploadAttachment');
request.setRequestHeader('Accept', 'Application/json');
request.setRequestHeader('Content-Type', 'multipart/related; boundary="xXxxxxxxxxxxxxxxxxXx"');
requestr.setStringParameterNoEscape('request_id', resultGr.correlation_id);
request.setRequestBodyFromAttachment(attachmentSysID);
var response = request.execute();
var statusCode = response.getStatusCode();
var responseBody = JSON.parse(response.getBody());
// Remove temporary attachment and record after upload
try {
attachment.deleteAttachment(attachmentSysID); // Delete temporary attachment
tempRecord.deleteRecord(); // Delete temporary record
gs.info('Temporary attachment and record successfully removed');
} catch (e) {
gs.error('Error while deleting temporary files: ' + e.message);
}
gs.info("The value of the POST Response is " + statusCode + "Body : " + responseBody);
// Log the upload result
gs.info('upload completed - Status Code: ' + statusCode);
gs.info('Response: ' + JSON.stringify(responseBody, null, 2));
return responseBody; // Return the upload result if needed
}
})(current, previous);
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
@SandyNow , The error occurs because Packages.java calls are not permitted in scoped applications. To resolve this, you should create the logic for generating the attachment file in a Global Scope Script Include.
Additionally, ensure that the Script Include is accessible from all scopes. This way, your Scoped Application can call the Global Scope Script, where the necessary processing will take place. Once the process is completed, you can continue handling the data within your scoped table as needed.
https://www.servicenow.com/docs/csh?topicname=c_ScriptIncludes.html&version=latest
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
@iwai Thanks for the direction. I had a doubt, in your script, I see we are creating a record in sys_poll table and deleting it. May I know the strategy behind this ?
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
The sys_poll table is commonly used for exporting processes, where export attachments are temporarily stored. In this case, the process involves sending data via REST API, and the request body needs to be converted into a binary file.
To ensure that the attachment file has an associated record (since attachments cannot exist without a parent record), a temporary sys_poll record is created. The binary data is then attached to this record. Once the data transmission is complete, the temporary record and its attachment are no longer needed, so they are deleted to keep the database clean.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hi @iwai ,
Thank you for this script. It would be helpful.
I need an urgent help on ServiceNow JIRA integration, without Integration Hub I am unable to send MultiPost/Multipart document to JIRA.
Below is my script, please let me know what corrections needs to be done.
Also if any query Parameter needs to be changed.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Uploading a ServiceNow Attachment to JIRA via REST API
I have created a script to upload an attachment from ServiceNow to a JIRA issue using the REST API. The implementation is based on the following cURL command:
curl -D- -u {username}:{password} -X POST -H "X-Atlassian-Token: nocheck" -F "file=@{path/to/file}" http://{base-url}/rest/api/2/issue/{issue-key}/attachments
This script retrieves an attachment from the ServiceNow sys_attachment table and posts it to the specified JIRA issue using the JIRA REST API.
/**
* Function to upload a ServiceNow attachment to JIRA
* @Param {string} sysAttachmentSysId - sys_id of the ServiceNow attachment
* @Param {string} issueKey - JIRA issue key where the file should be attached
* @returns {Object} Response from the JIRA API after upload
* @throws {Error} Throws an error if the attachment is not found or the API request fails
*/
function uploadFileToJira(sysAttachmentSysId, issueKey) {
var jiraBaseUrl = "http://{base-url}"; // JIRA
var jiraUsername = "{username}"; // JIRA
var jiraPassword = "{password}"; // JIRA
// Build the request for JIRA API
var request = new sn_ws.RESTMessageV2();
request.setHttpMethod('POST');
request.setEndpoint(jiraBaseUrl + '/rest/api/2/issue/' + issueKey + '/attachments');
request.setBasicAuth(jiraUsername, jiraPassword);
request.setRequestHeader('X-Atlassian-Token', 'nocheck');
request.setRequestHeader('Content-Type', 'multipart/form-data');
// Retrieve attachment information from ServiceNow
var grAttachment = new GlideRecord('sys_attachment');
if (!grAttachment.get(sysAttachmentSysId)) {
return;
}
// Get the file name and MIME type
var fileName = grAttachment.getValue('file_name');
var contentType = grAttachment.getValue('content_type');
// Retrieve the attachment data
var attachment = new GlideSysAttachment();
var fileBytes = attachment.getBytes(grAttachment);
// Construct multipart request body
var boundary = "xXxxxxxxxxxxxxxxxxXx";
var body = [
'--' + boundary,
'Content-Disposition: form-data; name="file"; filename="' + fileName + '"',
'Content-Type: ' + contentType,
'',
function (byteOutStr) {
byteOutStr.write(fileBytes, 0, fileBytes.length);
},
'',
'--' + boundary + '--'
];
// Build the binary data
var byteOutStr = new Packages.java.io.ByteArrayOutputStream();
for (var i = 0; i < body.length; i++) {
if (typeof body[i] !== 'function') {
var bytes = Packages.java.lang.String(body[i] + '\r\n').getBytes();
byteOutStr.write(bytes, 0, bytes.length);
} else {
body[i].call(this, byteOutStr);
}
}
// Create a temporary record and attachment
var tempRecord = new GlideRecord('sys_poll');
tempRecord.initialize();
tempRecord.message = 'Temporary attachment for JIRA';
tempRecord.insert();
var attachmentSysID = attachment.write(
tempRecord,
'PostBody',
'application/octet-stream',
byteOutStr.toByteArray()
);
byteOutStr.close();
I'm sharing this in case it helps others working on similar integrations. While I do my best to contribute as a fellow Community member, I am not in a dedicated support role and may not be able to provide further explanations or modifications to the script.
Community is a place for knowledge sharing and collaboration, not for requesting work. If you need additional tasks or in-depth investigations, please consider making a formal request to my company.
I hope this is helpful!
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hi ,
We have a requirement where we need to send multiple attachment using multipart ,does this also works for multiple attachments on a single record ?
Do we have anything that works for zip attachments?
Thanks,
Bharti
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Hello,
Based on the information you provided, I gathered various details and came to the conclusion that you are likely trying to communicate using the API at the following URL:
https://developer.atlassian.com/server/jira-servicedesk/rest/v1001/api-group-request-attachment/
1. Use the "Attach temporary files" endpoint in Request Attachment to upload a file and obtain a temporaryAttachmentId.
2. Then, use the "Add attachment" endpoint to set the temporaryAttachmentId in a JIRA request, registering the attachment to the request.
This implementation can be achieved by combining the methods I have already posted on this page. Please give it a try.
I regularly handle such customizations as part of my professional work. If you are considering this as a business request, feel free to reach out.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Yes, in ServiceNow, it is possible to send multiple attachments from a single record using multipart in a script. You can also send a ZIP file if needed. To implement this, you will need to clearly define the receiving system’s interface specifications, as well as the structure of the ServiceNow table and records. Based on that, script development, integration design, and proper testing should be carried out.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
@iwai Would really be helpful if you share more on this. We are also trying to send attachment to TopDesk following your code but everytime we are getting 415 as error. Could you please help.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
The HTTP 415 Unsupported Media Type error indicates that the server cannot process the request because the media type is not supported.
This error can occur in several situations, especially when sending files using multipart/form-data:
- The overall Content-Type header is missing, incorrect, or lacks the required boundary parameter.
- Individual parts of a multipart request have unsupported or incorrectly specified Content-Type headers.
- The declared Content-Type for a file does not match the actual content of the file. For example, uploading a PDF file while specifying Content-Type: image/png may trigger a 415 if the server performs MIME-type validation.
To troubleshoot this issue, using the curl command-line tool is highly recommended. With curl, you can:
- Explicitly set headers and inspect the raw HTTP request.
- Use the -F option to send files with multipart/form-data and specify the correct MIME type.
- Determine whether the problem lies in the client-side implementation or the server’s acceptance rules.
- Confirm whether the server accepts a correctly structured request when sent manually.
Example:
curl -X POST https://example.com/api/upload \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-F "file=@/path/to/test.pdf;type=application/pdf"
If the request succeeds with curl, it’s a strong indication that the issue lies within the application’s implementation.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
Sending a Multi-part REST API Request (Text + Binary) from ServiceNow
This script demonstrates how to successfully send a multi-part/form-data REST API request from ServiceNow, including both JSON metadata and binary (PDF) content as a single body using a custom boundary. This approach avoids MultipartHelper and works directly with a ByteArrayOutputStream.
Key Steps Covered:
Construct multi-part body manually with ByteArrayOutputStream
Insert JSON block as metadata part
Attach PDF as binary using GlideSysAttachmentInputStream
Convert to byte array, attach it to a record (e.g. sys_script_fix)
Send request with sn_ws.RESTMessageV2, using .setRequestBodyFromAttachment()
Save response body as an attachment for easy review
Authentication:
Uses Basic Auth (setBasicAuth) for simplicity, but can be adapted for OAuth if needed.
Endpoint Example:
Sent to https://httpbin.org/post for testing purposes.
This is particularly useful when standard Flow Designer or out-of-box REST capabilities fall short for complex multipart submissions, especially when mixing structured text and binary files in one call.
- Mark as Read
- Mark as New
- Bookmark
- Permalink
- Report Inappropriate Content
@iwai and @SasiChanthati
Thank you for your valuable feedback. I have managed to achieve it using Integration Hub as documented in the below link. Many a thanks for all the suggestions.
https://hi.service-now.com/kb_view.do?sysparm_article=KB0745010