logo

NJP

How to return a CSV file from a Scripted REST API (SRAPI) in ServiceNow

ServiceNow Developer Pro-Tips · Feb 07, 2022 · article

I recently dealt with a requirement whereby I needed to export only certain columns from records on a given table that match a given query, to a CSV file.

“_No problem_”, I said; “_that’s easy. You just add ‘_ **_?CSV_**_’ to the end of the table URL._”

Oh, but it’s never that easy, is it? In this case, there were a few odd stipulations:

* The header names need to be _specific strings_ (not the same as their column names in ServiceNow)
* Some of the columns need to have _calculated_ or _static values_; values not in ServiceNow (and we don’t want to make calculated columns for them)
* The endpoint must not return CSV data embedded in a JSON response body, but must actually return a CSV file. Or to put it more precisely, the endpoint URL must **resolve to a CSV _file_**.

Because of the nature of this requirement, I was going to need to create a Scripted REST API (SRAPI); so I did.

# Failures

First, I tried simply returning from the SRAPI script, some comma-delimited data like so:

When I hit the SRAPI URL, of course it didn’t work. I didn’t really expect it to, but figured it worth a try.
Instead, I saw the following:

> **Pro-tip**: You can hit a SRAPI from your browser using basic auth, like so:
> _https://user\_name:pass\_werd@myInstance.service-now.com/api/123/my\_api/endpoint_

So that obviously didn’t work. For some reason, ServiceNow is converting it to an XML file, and putting whatever I return into the _response.result_ node.

It’s a long-shot, but what if I go into the **Content Negotiation** section of the SRAPI form, check the **Override supported response formats** checkbox, and set the **Supported response formats** field to just “_text/csv_”…

Nope. That did nothing.

Okay, what if I explicitly add a line of code like _response.setHeader(‘Content-Type’, ‘text/csv’);_?

Nope, still didn’t do anything. Still returning an XML file. Wow, ServiceNow is really aggressive with its XMLization of everything, isn’t it?

# The Simple (Insecure) Solution

So I did a little digging, and eventually found the following solution, using _response.setHeader(‘Content-Type’, ‘text/csv’)_ (line 24), and _response.getStreamWriter().writeString()_ (line 27):

Hitting this SRAPI endpoint from a browser (or other tool) will now directly resolve to a CSV file.

But wait… What if the retrieved value contains commas? Or new-line characters? Or quote-marks? Those are CSV control characters, so we’ll need to deal with them.

We can handle commas and new-line characters by simply wrapping each cell’s data in double-quotes; but first, we have to escape any double-quote characters already in the original string. While CSV has no strict standard (even [RFC4180](https://www.rfc-editor.org/rfc/rfc4180) “_does not specify an internet standard of any kind_”), the generally accepted way to escape a quote, is with another quote (““), so we’ll do that.

Alright, that’s solved. But wait! By default, anyone that isn’t an _external user_ (with the _snc\_external_ role) can hit a REST API endpoint and get what it returns… Because _GlideRecord_ ignores ACLs, this would allow any internal user to access pretty much any data in the entire system! We certainly don’t want that!

Let’s add some logic to check that the API user can actually see the specified record, and every column we’re exporting.
If they can’t see the record, we’ll skip it and try the next one while logging a warning. If they can see the record (meaning that they have table and record-level access) but not a specific requested column, then we’ll log a warning, set that column’s value to a blank string for that record, and continue to the next column.

Finally, we’ll push the escaped field value into our _currentRow_ array, for inclusion in the final CSV.

# The Robust Solution

As you can see, I added a bunch more error detection and handling code in this version, as well as some additional security measures. Thanks to a pro-tip from Ben in the comments below this article, I’ve also added a line to set the resultant file name for the CSV file that this endpoint will resolve to.

This code should work well enough as-is, that you can pretty much just copy-and-paste it into your environment and be good to go!

# Constructing the API URI

To construct the API call, you first have to construct the object that’ll go into the URL. This will be a JSON object with the following structure:

{
"table_name": "some_table_name",
"table_query": "some_field=some_value^other_field=something_else",
"query_limit": 100,
"columns": [
{
"source_column_name": "number",
"destination_column_name": "Ticket number"
},
{
"source_column_name": "assigned_to",
"destination_column_name": "Assigned to"
}
]
}

In the **columns** array, each object has a **source\_column\_name** field which corresponds to the name of the field in ServiceNow, and a **destination\_column\_name** field, which corresponds to the name to go into the column header in the resulting CSV.

Once you’ve got the object constructed, take the SRAPI endpoint (like and add the URI parameter **?map=**, after which you can just copy-and-paste your JSON object. If you paste the object into your URL bar, it’ll remove all new-lines for you, and flatten it out.

If you want to test it out, you can add the basic auth prefix to the URL after the “ That would look like:

```
https://USERNAME:PASSWORD@INSTANCE_NAME.service-now.com/...
```

Putting it all together, the final URL might look something like this:

```
https://USERNAME:PASSWORD@instance_name.service-now.com/api/12345/my-api/export-to-csv?map={%20%22table_name%22:%20%22some_table_name%22,%20%22table_query%22:%20%22some_field=some_value^other_field=something_else%22,%20%22query_limit%22:%20100,%20%22columns%22:%20[%20{%20%22source_column_name%22:%20%22number%22,%20%22destination_column_name%22:%20%22Ticket%20number%22%20},%20{%20%22source_column_name%22:%20%22assigned_to%22,%20%22destination_column_name%22:%20%22Assigned%20to%22%20}%20]%20}
```

---

## Subscribe

Sign up with your email address to receive news and updates.

First Name Last Name

Email Address Sign Up

We respect your privacy.

Thank you!

* [ 2026](https://snprotips.com/blog?year=2026)
* Jan 8, 2026 [Flow Designer vs. Scripting - REST Message Performance](https://snprotips.com/blog/) Jan 8, 2026
* [ 2025](https://snprotips.com/blog?year=2025)
* Sep 29, 2025 [Find Filthy Inefficient Single-Record Queries FAST](https://snprotips.com/blog/) Sep 29, 2025
* Sep 14, 2025 [Communicating Changes to Your Users (& Setting Default User Preferences in ServiceNow)](https://snprotips.com/blog/) Sep 14, 2025
* Mar 24, 2025 [Calculate Distance Between Two Locations in ServiceNow (without an API call!)](https://snprotips.com/blog/) Mar 24, 2025
* Mar 11, 2025 [5 Ways to Check your ServiceNow Instance for DANGEROUS CODE in Less Than 5 minutes](https://snprotips.com/blog/) Mar 11, 2025
* [ 2024](https://snprotips.com/blog?year=2024)
* Mar 28, 2024 [How to Identify Duplicate Records by Multiple Fields in ServiceNow](https://snprotips.com/blog/) Mar 28, 2024
* Mar 7, 2024 [How to Merge Personal & Company ServiceNow Accounts](https://snprotips.com/blog/) Mar 7, 2024
* Feb 12, 2024 [5 Lessons About Programming From Richard Feynman](https://snprotips.com/blog/) Feb 12, 2024
* [ 2023](https://snprotips.com/blog?year=2023)
* Jul 5, 2023 [Managing Instance-Specific System Properties for Dev/Test/Prod in ServiceNow](https://snprotips.com/blog/) Jul 5, 2023
* Apr 28, 2023 [Your ACLs and Business Rules are Broken (Here's How to Fix Them)](https://snprotips.com/blog/) Apr 28, 2023
* [ 2022](https://snprotips.com/blog?year=2022)
* Dec 13, 2022 [ServiceNow Developers: BE THE GUIDE!](https://snprotips.com/blog/) Dec 13, 2022
* Oct 19, 2022 [A Faster, More Efficient Client-side GlideRecord (Free tool!)](https://snprotips.com/blog/) Oct 19, 2022
* Oct 9, 2022 [Animated Loading Message & Collapsible Details on ServiceNow Form or Field (Client-side)](https://snprotips.com/blog/) Oct 9, 2022
* Aug 23, 2022 [Using .addJoinQuery() & How to Query Records with Attachments in ServiceNow](https://snprotips.com/blog/) Aug 23, 2022
* Aug 18, 2022 [Free, Simple URL Shortener for ServiceNow Nerds (snc.guru)](https://snprotips.com/blog/) Aug 18, 2022
* Aug 16, 2022 [How to Get and Parse ServiceNow Journal Entries as Strings/HTML](https://snprotips.com/blog/) Aug 16, 2022
* Aug 14, 2022 [New tool: Get Latest Version of ServiceNow Docs Page](https://snprotips.com/blog/) Aug 14, 2022
* Mar 4, 2022 [How to Set or Change ServiceNow Application's Repository URL, Credentials, or SSH Key](https://snprotips.com/blog/) Mar 4, 2022
* Feb 7, 2022 [How to return a CSV file from a Scripted REST API (SRAPI) in ServiceNow](https://snprotips.com/blog/) Feb 7, 2022
* [ 2021](https://snprotips.com/blog?year=2021)
* May 3, 2021 [Adding a Guided Setup to Your ServiceNow Application](https://snprotips.com/blog/) May 3, 2021
* Apr 27, 2021 [Use Automated Tests to Validate "Guided Setup" Completion & Functionality.](https://snprotips.com/blog/) Apr 27, 2021
* Feb 11, 2021 ["Processors", SRAPIs, and How to Run a Script and Redirect a User From a URL in ServiceNow](https://snprotips.com/blog/) Feb 11, 2021
* [ 2020](https://snprotips.com/blog?year=2020)
* Nov 17, 2020 [SN Guys is now part of Jahnel Group!](https://snprotips.com/blog/) Nov 17, 2020
* Sep 14, 2020 [Better ServiceNow Notifications (& Another FREE Tool!)](https://snprotips.com/blog/) Sep 14, 2020
* Jul 31, 2020 [Debugging Client & Catalog Client Scripts in ServiceNow](https://snprotips.com/blog/) Jul 31, 2020
* Jan 20, 2020 [Getting Help from the ServiceNow Community](https://snprotips.com/blog/) Jan 20, 2020
* [ 2019](https://snprotips.com/blog?year=2019)
* Dec 18, 2019 [Can ServiceNow Script Includes Use the "current" Variable?](https://snprotips.com/blog/) Dec 18, 2019
* Nov 18, 2019 [Handling 'text/plain' and Other Unsupported Content Types in ServiceNow Scripted REST APIs](https://snprotips.com/blog/) Nov 18, 2019
* Apr 21, 2019 [Understanding Attachments in ServiceNow](https://snprotips.com/blog/) Apr 21, 2019
* Apr 10, 2019 [Using Custom Search Engines in Chrome to Quickly Navigate ServiceNow](https://snprotips.com/blog/) Apr 10, 2019
* Apr 4, 2019 [Set Catalog Variables from URL Params (Free tool)](https://snprotips.com/blog/) Apr 4, 2019
* Apr 1, 2019 [Outlook for Android Breaks Email Approvals (+Solution)](https://snprotips.com/blog/) Apr 1, 2019
* Mar 11, 2019 [GlideFilter is Broken - Free Tool: “BetterGlideFilter”](https://snprotips.com/blog/) Mar 11, 2019
* Feb 27, 2019 [Making Update Sets Smarter - Free Tool](https://snprotips.com/blog/) Feb 27, 2019
* [ 2018](https://snprotips.com/blog?year=2018)
* Nov 29, 2018 [How to Learn ServiceNow](https://snprotips.com/blog/) Nov 29, 2018
* Nov 6, 2018 [ServiceNow & ITSM as a Career?](https://snprotips.com/blog/) Nov 6, 2018
* Oct 19, 2018 [Asynchronous onSubmit Catalog/Client Scripts in ServiceNow](https://snprotips.com/blog/) Oct 19, 2018
* Oct 11, 2018 [How to do Massive, Slow Database Operations Efficiently With Event-Driven Recursion](https://snprotips.com/blog/) Oct 11, 2018
* Sep 18, 2018 [Broken Queries & Query Business Rules in ServiceNow](https://snprotips.com/blog/) Sep 18, 2018
* Sep 7, 2018 [JournalRedactor - Easily Redact or Delete Journal Entries in ServiceNow!](https://snprotips.com/blog/) Sep 7, 2018
* Jul 23, 2018 [Admin Duty Separation with a Single Account](https://snprotips.com/blog/) Jul 23, 2018
* Jun 19, 2018 [Improving Performance on Older Instances with Table Rotation](https://snprotips.com/blog/) Jun 19, 2018
* Jun 4, 2018 [New Free Tool: Login Link Generator](https://snprotips.com/blog/) Jun 4, 2018
* May 29, 2018 [Learning ServiceNow: Second Edition!](https://snprotips.com/blog/) May 29, 2018
* Apr 17, 2018 [Upgrading From Express to Enterprise: What's Missing](https://snprotips.com/blog/) Apr 17, 2018
* Apr 12, 2018 [If a Genie Gave Me Three Wishes, I'd Use Them All to "Fix" Scope](https://snprotips.com/blog/) Apr 12, 2018
* Mar 19, 2018 [Service Catalog "Try in Portal" button](https://snprotips.com/blog/) Mar 19, 2018
* Mar 15, 2018 [Video: Custom Output Transition Conditions From a Single Workflow (Script) Activity](https://snprotips.com/blog/) Mar 15, 2018
* Feb 11, 2018 [We have a new book! ](https://snprotips.com/blog/) Feb 11, 2018
* [ 2017](https://snprotips.com/blog?year=2017)
* Nov 6, 2017 [Requiring Attachments (& Other Miracles) in Service Portal](https://snprotips.com/blog/) Nov 6, 2017
* Sep 12, 2017 [Handling TimeZones in ServiceNow (TimeZoneUtil)](https://snprotips.com/blog/) Sep 12, 2017
* Jul 27, 2017 [How to Enable DOM Manipulation in ServiceNow Service Portal Catalog Client Scripts](https://snprotips.com/blog/) Jul 27, 2017
* Jun 25, 2017 [What's New in ServiceNow: Jakarta (Pt. 1)](https://snprotips.com/blog/) Jun 25, 2017
* Jun 4, 2017 [Powerful Scripted Text Search in ServiceNow](https://snprotips.com/blog/) Jun 4, 2017
* May 9, 2017 [Work at Lightspeed: ServiceNow's Plan for World Domination](https://snprotips.com/blog/) May 9, 2017
* Apr 9, 2017 [Avoiding Pass-By-Reference Using getValue() & setValue()](https://snprotips.com/blog/) Apr 9, 2017
* Apr 4, 2017 ["Learning ServiceNow" is Now Available for Purchase!](https://snprotips.com/blog/) Apr 4, 2017
* Mar 12, 2017 [reCAPTCHA in ServiceNow CMS/Service Portal](https://snprotips.com/blog/) Mar 12, 2017
* [ 2016](https://snprotips.com/blog?year=2016)
* Dec 20, 2016 [Pro Tip: Use updateMultiple() for Maximum Efficiency! ](https://snprotips.com/blog/) Dec 20, 2016
* Dec 2, 2016 [We're Writing a Book! ](https://snprotips.com/blog/) Dec 2, 2016
* Nov 10, 2016 [Chrome Extension: Load in ServiceNow Frame](https://snprotips.com/blog/) Nov 10, 2016
* Sep 7, 2016 [Force-Include Any Record Into an Update Set](https://snprotips.com/blog/) Sep 7, 2016
* Sep 1, 2016 [GlideRecord Pagination - Page through your GlideRecord query](https://snprotips.com/blog/) Sep 1, 2016
* Jul 17, 2016 [Granting Temporary Roles/Groups in ServiceNow](https://snprotips.com/blog/) Jul 17, 2016
* Jul 15, 2016 [Scripted REST APIs & Retrieving RITM Variables via SRAPI](https://snprotips.com/blog/) Jul 15, 2016
* May 17, 2016 [What's New in Helsinki?](https://snprotips.com/blog/) May 17, 2016
* Apr 27, 2016 [Customizing UI16 Through CSS and System Properties](https://snprotips.com/blog/) Apr 27, 2016
* Apr 5, 2016 [ServiceNow Versions: Express Vs. Enterprise](https://snprotips.com/blog/) Apr 5, 2016
* Mar 28, 2016 [Update Set Collision Avoidance Tool: V2](https://snprotips.com/blog/) Mar 28, 2016
* Mar 18, 2016 [ServiceNow: What's New in Geneva & UI16 (Pt. 2)](https://snprotips.com/blog/) Mar 18, 2016
* Feb 22, 2016 [Reference Field Auto-Complete Attributes](https://snprotips.com/blog/) Feb 22, 2016
* Feb 6, 2016 [GlideRecord & GlideAjax: Client-Side Vs. Server-Side](https://snprotips.com/blog/) Feb 6, 2016
* Feb 1, 2016 [Make Your Log Entries Easier to Find](https://snprotips.com/blog/) Feb 1, 2016
* Jan 29, 2016 [A Better, One-Click Approval](https://snprotips.com/blog/) Jan 29, 2016
* Jan 25, 2016 [Quickly Move Changes Between Update Sets](https://snprotips.com/blog/) Jan 25, 2016
* Jan 20, 2016 [Customize the Reference Icon Pop-up](https://snprotips.com/blog/) Jan 20, 2016
* Jan 7, 2016 [ServiceNow: Geneva & UI16 - What's new](https://snprotips.com/blog/) Jan 7, 2016
* Jan 4, 2016 [Detect/Prevent Update Set Conflicts Before They Happen](https://snprotips.com/blog/) Jan 4, 2016
* [ 2015](https://snprotips.com/blog?year=2015)
* Dec 28, 2015 [SN101: Boolean logic and ServiceNow's Condition Builder](https://snprotips.com/blog/) Dec 28, 2015
* Dec 17, 2015 [Locate any record in any table, by sys\_id in ServiceNow](https://snprotips.com/blog/) Dec 17, 2015
* Dec 16, 2015 [Detecting Duplicate Records with GlideAggregate](https://snprotips.com/blog/) Dec 16, 2015
* Dec 11, 2015 [Array.indexOf() not working in ServiceNow - Solution! ](https://snprotips.com/blog/) Dec 11, 2015
* Dec 2, 2015 [Understanding Dynamic Filters & Checking a Record Against a Filter Using GlideFilter](https://snprotips.com/blog/) Dec 2, 2015
* Oct 20, 2015 [Bookmarklet: Load the current page in the ServiceNow frame](https://snprotips.com/blog/) Oct 20, 2015
* Aug 27, 2015 [Easily Clone One User's Access to Another User](https://snprotips.com/blog/) Aug 27, 2015

View original source

https://snprotips.com/blog/2022/2/7/how-to-return-a-csv-file-from-a-servicenow-scripted-rest-api