The Zurich release has arrived! Interested in new features and functionalities? Click here for more

Scripted REST API to upload attachments to a table that blocks Table API.

vamsi kuruvella
Tera Contributor

 

Hello All,
I’m building a Scripted REST API to allow a trusted 3rd-party system to upload files to ServiceNow. We can’t use the standard Attachment API because, :

 

  • The Attachment API requires a target record sys_id and table name.
  • The target table (sn_hr_ef_employee_document) does not allow record creation via Table API (business constraint). 

So I created a Scripted REST endpoint which:

  1. Accepts a JSON header Attachment-Details containing metadata (FileName, FileType, EmployeeNumber).

  2. Accepts the raw file stream in request.body.dataStream.

Creates/writes the attachment using GlideSysAttachment.writeContentStream() against an existing record.

 

Note:

I’m sharing this here mostly as documentation, since I don’t currently have access to create a Blog post 🙂 . It covers how I built a Scripted REST API for file uploads in ServiceNow. Since the standard Attachment API requires an existing record sys_id and table name (and my target table blocks Table API creation), I had to roll my own solution.

I’ll document the background, design choices, and sample code here so it might help others in the same situation.

If you just want to read, treat this as a reference.
If you’d like to discuss, I’ve added a “Questions for the community” section at the end where I’d love input.


Below is the current implementation I’m using:

`

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

try {
var gr1 = new GlideRecord("incident");
if (gr1.get("rec_syd_id")) {
var attachmentDetails = JSON.parse(request.getHeader("Attachment-Details") || "null");

if (!attachmentDetails) {
response.setStatus(400);
response.setBody({
status: "Error",
message: "Attachment Details not provided."
});
return;
}
if (!attachmentDetails.FileName) {
response.setStatus(400);
response.setBody({
status: "Error",
message: "FileName not found in Attachment Details."
});
return;
}

if (!attachmentDetails.EmployeeNumber) {
response.setStatus(400);
response.setBody({
status: "Error",
message: "EmployeeNumber not found in Attachment Details."
});
return;
}

if (!attachmentDetails.FileType) {
response.setStatus(400);
response.setBody({
status: "Error",
message: "FileType not found in Attachment Details."
});
return;
}
var fileName = attachmentDetails.FileName;
var contentType = attachmentDetails.FileType;
var empNum = attachmentDetails.EmployeeNumber;
var gsa = new GlideSysAttachment();
gsa.writeContentStream(gr1, fileName, contentType, request.body.dataStream);
response.setStatus(201);
response.setBody({
status: "Success",
targetRec: gr1.sys_id.toString(),
filename: fileName
});
}

} catch (ex) {
response.setStatus(500);
response.setBody({
status: "Error",
message: ex.message
});
gs.error("File upload failed: " + ex);
}

})(request, response);

`

I tried to insert "Code Sample", but i was getting "Invalid HTML" error 😞 , so i have to paste as string.

How this works

  • The endpoint expects the file bytes in request.body.dataStream.

  • Metadata comes in a header Attachment-Details (JSON): { "FileName": "doc.pdf", "FileType": "application/pdf", "EmployeeNumber": "EMP001" }.

  • The code writes the stream as an attachment to an existing record using GlideSysAttachment.writeContentStream().

Why I did it this way

  • The target table disallows record creation via Table API, and the Attachment API requires an existing record — so I attach to an allowed existing record (e.g. a staging incident or a dedicated upload record) and then use server-side logic / business rules to move or relate the attachment to the Employee Document record once internal checks pass.

Sample Postman test:

vamsikuruvella_0-1758430764406.png

 

Headers

HeaderValue
Attachment-Details{"FileName":"Testpdf.pdf","EmployeeNumber":"EMP001","FileType":"application/pdf"}
Content-Type "application/allow-all"

"application/allow-all" is my creation, since Scripted REST APIs don't allow empty "Default supported request formats" to be empty.

Questions for the community

  1. Have folks used GlideSysAttachment.writeContentStream() for large files? Any practical file size limits encountered?
  2. I tried using multipart/form-data with my Scripted REST API, but ServiceNow seems to pass the entire request body as a single binary stream. I wasn’t able to parse the individual parts (file + metadata) — I only get the raw stream, not the separated form-data fields.

    Has anyone successfully handled multipart form-data in a Scripted REST API?

Looking forward to your responses, Thank you 🙂 .

 

0 REPLIES 0