Building a managed API (From REST consumer to API provider 3/3)
Share on socials

Building a managed API (From REST consumer to API provider 3/3)
Welcome to the From REST consumer to API provider guest tutorial. In this three-part series, Rafael Pinto Sperafico from The Adaptavist Group partner, Ambientia, will guide you through the world of REST APIs. First we got you started with the fundamentals in Getting to grips with the basics of REST APIs. Then in tutorial two, REST APIs made easy with ScriptRunner Connect, we created scripts for basic REST API operations, including GET, POST, PUT, PATCH, and DELETE requests, using the ReqRes API.
Now, in the final post of the series, we take things further by transforming our consumer-oriented scripts into a reusable, managed API. We'll build a TypeScript-based package that encapsulates the same operations, making the logic portable and maintainable. Additionally, we’ll demonstrate how to publish the managed API to npm and integrate it into ScriptRunner Connect.
What you will learn:
1. How to configure TypeScript with tsconfig.json
2. How to define types in ReqRes.js
3. How to create ReqResAPI.js
4. How to publish a managed API to npm
5. How to import into ScriptRunner Connect
Setting up the TypeScript project
To get started, let’s create a directory on your local environment so we can build our TypeScript-based package.
Create a new project folder:
mkdir reqres-api
cd reqres-api
npm init -y
Update package.json with the code below.
Important: This tutorial uses the package name "reqres-api". When following this tutorial, please replace this with a unique package name and note it down for use later.
package.json
{
"name": "reqres-api",
"version": "1.0.0",
"description": "A managed API client for ReqResIn.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"scripts": {
"build": "tsc"
},
"keywords": ["api", "reqres", "typescript"],
"author": "Rafael Pinto Sperafico",
"license": "MIT",
"devDependencies": {
"typescript": "^5.0.2"
},
"files": ["dist", "README.md"],
"publishConfig": {
"access": "public"
},
"dependencies": {
"@managed-api/generic-core": "^1.2.0"
}
}
Explanation:
- main: Specifies the entry point for the compiled JavaScript code.
- types: Points to the compiled TypeScript declarations for type safety in consuming projects.
- scripts: Build runs the TypeScript compiler.
- files: Specifies which files to include in the published npm package.
- publishConfig: Ensures the package is public by default.
Configure TypeScript with tsconfig.json
Create
tsconfig.json
in the project root:tsconfig.json
{
"compilerOptions": {
"target": "ES2019",
"module": "ES6",
"outDir": "dist",
"strict": true,
"declaration": true,
"esModuleInterop": true,
"moduleResolution": "node"
},
"include": ["src/**/*"]
}
Explanation:
- target: Outputs code compatible with ES2019.
- module: Uses the ES6 module system.
- outDir: Outputs compiled files to the
dist
directory. - strict: Enables strict type checking.
- declaration: Generates
.d.ts
files for type definitions. - esModuleInterop: Ensures compatibility with CommonJS modules.
- include: Includes all TypeScript files in the
src
directory.
Project structure
Create the following structure:
./src/index.ts
./src/ReqResAPI.ts
./src/ReqResType.ts
Define types in ReqResType.js
In this step, you define TypeScript interfaces that describe the structure of the data exchanged with the API. These include:
- User: Represents a user object with fields like
id
,email
,first_name
, etc. - PaginatedUsersResponse: Models paginated responses, including metadata like page and the list of users (data).
- CreateUserRequest/Response: Structures the payload for creating users and the server's response, including generated fields like id and timestamps.
- UpdateUserResponse: Similar to
CreateUserResponse
, but used for updates.
Defining types ensures type safety, reducing errors and improving code clarity.
src/ReqResType.ts
/**
* Represents a user object
*/
export interface User {
id: number;
email: string;
first_name: string;
last_name: string;
avatar: string;
}
/**
* Represents paginated responses for fetching users
*/
export interface PaginatedUsersResponse {
page: number;
per_page: number;
total: number;
total_pages: number;
data: User[];
}
/**
* Defines the structure of a user creation request
*/
export interface CreateUserRequest {
name: string;
job: string;
}
/**
* Combines request fields with server-generated fields
*/
export interface CreateUserResponse extends CreateUserRequest {
id: string;
createdAt: string;
}
/**
* Similar to 'CreateUserResponse' but for updates
*/
export interface UpdateUserResponse extends CreateUserRequest {
updatedAt: string;
}
Create ReqResAPI.ts
By encapsulating the operations into a reusable TypeScript package, we improve maintainability and create a scalable foundation for future integrations. This managed API can be easily imported and used in any ScriptRunner Connect workspace, unlocking the full potential of API automation.
Define the class and constructor
src/ReqResAPI.ts
import {
PaginatedUsersResponse,
CreateUserRequest,
CreateUserResponse,
UpdateUserResponse
} from './ReqResType';
//import { GenericAppApiCore } from "@managed-api/generic-core";
/**
* ReqResAPI provides a wrapper around the ReqRes API client to perform user operations.
* It includes methods for fetching, creating, updating, and deleting users.
*/
export class ReqResAPI {
private client: any;
// private client: GenericAppApiCore;
/**
* Creates an instance of ReqResAPI.
* @param client - An instance of the Generic Connector created in ScriptRunner Connect.
*/
constructor(client: any) {
this.client = client;
}
// Handle Requests
// Implement API Methods
}
Handle requests
The
handleRequest
method centralises the logic for executing API requests and handling responses.- Purpose: Simplifies error handling and data parsing by wrapping request logic in a single method.
- Error management: Returns a consistent structure with
data
orerror
fields, ensuring robust and predictable handling of API responses.
src/ReqResAPI.ts
// Handle Requests
/**
* Handles the execution of an API request.
* @param request - A function that returns a `Response` object when invoked.
* @returns A promise resolving to an object containing the parsed data or an error message.
*/
private async handleRequest<T>(request: () => Promise<Response>): Promise<{ data?: T; error?: string }> {
try {
const response = await request();
if (!response.ok) {
const errorText = await response.text();
return { error: `Unexpected response code: ${response.status} - ${errorText}` };
}
const data = await response.json();
return { data };
} catch (err) {
return { error: `Network or parsing error: ${(err as Error).message}` };
}
}
Implement API methods
This involves adding specific methods in the
ReqResAPI
class for each API operation:- getUsers: Fetches users with pagination.
- createUser: Sends
POST
requests to create a user. - updateUser: Supports both
PUT
(complete update) andPATCH
(partial update) operations. - deleteUser: Deletes a user by ID.
Each method uses handleRequest to standardise request execution and error processing.
GET request: Fetch users
- ScriptRunner Connect: In the previous post, we hardcoded the
GET
request to fetch the second page of users using the ReqRes API. This script used query parameters likepage
for pagination. - Managed API: The
getUsers(pageNumber)
method in theReqResAPI
class implements this logic, dynamically accepting thepageNumber
as a parameter. It uses a privatehandleRequest
method to manage API responses and errors uniformly, improving flexibility and error handling.
src/ReqResAPI.ts
// Implement API Methods
/**
* Fetches a paginated list of users.
* @param pageNumber - The page number to fetch.
* @returns A promise that resolves to an object containing user data or an error message.
*/
async getUsers(pageNumber: number): Promise<{ data?: PaginatedUsersResponse; error?: string }> {
return this.handleRequest<PaginatedUsersResponse>(() => this.client.fetch(`/users?page=${pageNumber}`));
}
POST request: Create a user
- ScriptRunner Connect: The script demonstrated creating a user with hardcoded data, including
name
andjob
. While functional, it lacked flexibility for dynamic input. - Managed API: The
createUser(data: CreateUserRequest)
method in the managed API allows passing dynamic user data as parameters, improving usability. It ensures consistency with server responses by leveraging TypeScript's interfaces.
src/ReqResAPI.ts
// Implement API Methods
/**
* Creates a new user.
* @param data - The user details including name and job title.
* @returns A promise that resolves to the created user data or an error message.
*/
async createUser(data: CreateUserRequest): Promise<{ data?: CreateUserResponse; error?: string }> {
return this.handleRequest<CreateUserResponse>(() =>
this.client.fetch('/users', {
method: 'POST',
headers: { 'Content-type': 'application/json' },
body: JSON.stringify(data),
})
);
}
PUT & PATCH request: (Partially) update a user
- ScriptRunner Connect: The
PUT
script updated a user with static data and a hardcodeduserId
, and thePATCH
script updated only specific fields using hardcoded values. - Managed API: The
updateUser(userId, data, method)
method adds dynamic capabilities by allowing bothPUT(complete update)
andPATCH (partial update)
operations. It accepts a flexiblemethod
parameter to define the type of update, consolidating the logic for both operations.
src/ReqResAPI.ts
// Implement API Methods
/**
* Updates an existing user.
* @param userId - The ID of the user to update.
* @param data - The new user details including name and job title.
* @param method - Define how update should happen, in fully (PUT) or partial (PATCH)
* @returns A promise that resolves to the updated user data or an error message.
*/
async updateUser(userId: number, data: CreateUserRequest, method: 'PUT' | 'PATCH'): Promise<{
data?:
UpdateUserResponse; error?: string
}> {
return this.handleRequest<UpdateUserResponse>(() =>
this.client.fetch(`/users/${userId}`, {
method: method,
headers: { 'Content-type': 'application/json' },
body: JSON.stringify(data),
})
);
}
DELETE request: Remove a user
- ScriptRunner Connect: The script was hardcoded to delete a specific user by
userId
, which limited its usability. - Managed API: The
deleteUser(userId)
method in the managed API improves on this by accepting a dynamicuserId
, ensuring it can delete any user specified at runtime. It also checks for expected status codes, maintaining consistency with best practices.
// Implement API Methods
/**
* Deletes a user by ID.
* @param userId - The ID of the user to delete.
* @returns A promise that resolves to `true` if the user was deleted successfully or an error message.
*/
async deleteUser(userId: number): Promise<{ data?: boolean; error?: string }> {
return this.handleRequest<boolean>(async () => {
const response = await this.client.fetch(`/users/${userId}`, { method: 'DELETE' });
if (response.ok) {
return new Response(JSON.stringify(true)); // Simulate a JSON `true` response for consistency
}
return response;
});
}
Export everything in index.ts
The
index.ts
file acts as the module's entry point:- Exports the
ReqResAPI
class and all defined types. - Facilitates easy import of the API functionality in other projects by bundling all exports in a single file.
src/index.ts
export { ReqResAPI } from './ReqResAPI';
export * from './ReqResType';
Build the project
Run the following command:
npm run build
This compiles TypeScript into JavaScript and generates the
dist
directory.Create an npm account
- Visit npmjs.com.
- Sign up and verify your email.
- Log in via the terminal:
npm login
Publish the package
This step involves making the API client publicly available as an npm package:
- Build: Transpile TypeScript to JavaScript.
- Setup: Use
package.json
to configure package metadata, including dependencies and publish settings. - Publish: Upload the package to npm using the
npm publish
command to make it accessible to other developers and applications.
Publishing allows the managed API to be reused across multiple projects, ensuring a scalable and maintainable solution.
Use in ScriptRunner Connect
Add dependency
- From your workspace in ScriptRunner Connect, click on the three dots in the top right-hand side of your screen and choose Package Manager.
- Select the Third Party Packages tab
- Under Add an unverified npm package search for reqres-api and click Add.
Import and use
- Navigate to the Scripts tab in your workspace.
- Click + to create a new script, give it a name (e.g., "managedAPI"), and select TypeScript as the language.
- Import API Connection.
- Import the Managed API you have published in npm.
managedAPI
// API Connection
import ReqresInClient from './api/generic';
// Managed API
import { ReqResAPI } from 'reqres-api'
export default async function (event: any, context: Context): Promise<void> {
console.log('Script triggered', event, context);
// Create a Managed API instance
const managedAPI = new ReqResAPI(ReqresInClient);
// Fetch Users
// Fetch All Users
// Create a User
// Update a User
// Partially Update a User
// Remove a User
}
GET request: Fetch users
Explanation:
- Initialisation: Searches for users from a given page, e.g 2
- Error handling: If an error occurs while fetching a page, the function logs the error and returns the collected users so far.
// Fetch Users
// Fetch users from page 2
const users = await managedAPI.getUsers(2);
console.log("Users", users);
GET request: Fetch all users
To fetch all users using recursion, you can create a function that calls itself until all pages are fetched. Here's how you can implement this:
Explanation:
- Recursive function:
fetchAllUsers(currentPage, totalPages, users)
handles the recursive logic.- Parameters:
currentPage
: Tracks the current page.totalPages
: Total number of pages to fetch (determined dynamically from the first response).users
: Accumulates user data across recursive calls.
- Base case: If
currentPage
exceedstotalPages
, the function stops and returns the accumulatedusers
array. - Recursive case: The function calls itself with
currentPage + 1
until all pages are fetched. - Initialisation: The recursion starts with
currentPage = 1
andtotalPages
undefined. ThetotalPages
value is set based on the first API response. - Error handling: If an error occurs while fetching a page, the function logs the error and returns the collected users so far.
This recursive approach dynamically handles pagination and keeps the logic clean by leveraging function calls for each page fetch.
// Fetch All Users
// Function to recursively fetch all users
const fetchAllUsers = async (currentPage: number, totalPages?: number, users: any[] = []): Promise<any[]> => {
// Fetch users for the current page
const response = await managedAPI.getUsers(currentPage);
if (response.error) {
console.error(`Error fetching users on page ${currentPage}:`, response.error);
return users; // Return the collected users even if there's an error
}
const { data } = response;
if (data) {
// Append the current page's users to the collected list
const updatedUsers = [...users, ...data.data];
// If totalPages is undefined (first call), initialise it
if (!totalPages) {
totalPages = data.total_pages;
}
// Base case: If we have fetched the last page, return all users
if (currentPage >= totalPages) {
return updatedUsers;
}
// Recursive case: Fetch the next page
return fetchAllUsers(currentPage + 1, totalPages, updatedUsers);
}
// Return collected users if no data is returned
return users;
};
// Start fetching from page 1
const allUsers = await fetchAllUsers(1);
console.log('All Users:', allUsers);
POST request: Create a user
To call the
create a user
method using the managedAPI
instance, you can use the createUser
method provided by the ReqResAPI
class. This method expects a parameter containing the user’s details, such as their name
and job
.Explanation:
- User data:
- Define the
userData
object with the necessary fields:name
: The name of the user.job
: The job title of the user.
- Define the
- Method call: Use
await managedAPI.createUser(userData)
to create a user. The method sends aPOST
request to the API with the provided user data. - Response handling: Check if the
response.error
exists to handle any errors during the API call.- If no error, access the
response.data
to retrieve the details of the created user, including theirid
andcreatedAt
timestamp.
- If no error, access the
// Create a User
// Define the user details to create
const userData = {
name: 'Jane Doe',
job: 'Software Developer'
};
// Call the createUser method
const response = await managedAPI.createUser(userData);
if (response.error) {
console.error('Error creating user:', response.error);
} else {
console.log('User Created Successfully:', response.data);
}
Sample output:
If the user is created successfully, the console might log:
User Created Successfully: {
id: "573",
name: "Jane Doe",
job: "Software Developer",
createdAt: "2024-12-03T12:34:56.789Z"
}
This makes it easy to dynamically create users by varying the
userData
object as needed.PUT request: Update a user
To update a user using the
PUT
method with the managedAPI
instance, you can use the updateUser
method in the ReqResAPI
class. This method requires the userId
to identify the user and the updated details for the user.Explanation:
1. User identification:
userId
: Specify the ID of the user you want to update.2. Updated data:
updatedData
: An object containing the new name
and job
values.3. Method call:
Use
await managedAPI.updateUser(userId, updatedData, 'PUT')
to send a complete update to the user data using the PUT
method.4. Response handling:
- Check if
response.error
exists to identify any issues during the request. - Access
response.data
to view the updated user details if the request is successful.
// Update a User
// Define the user ID and updated details
const userId = 2; // Replace with the ID of the user to update
const updatedData = {
name: 'John Smith',
job: 'Senior Developer'
};
// Call the updateUser method with PUT
const response = await managedAPI.updateUser(userId, updatedData, 'PUT');
if (response.error) {
console.error('Error updating user:', response.error);
} else {
console.log('User Updated Successfully:', response.data);
}
Sample output:
If the user is updated successfully, the console might log:
User Updated Successfully: {
name: "John Smith",
job: "Senior Developer",
updatedAt: "2024-12-03T12:34:56.789Z"
}
This approach ensures that the entire user object is replaced with the provided data when using the
PUT
method.PATCH request: Partially update a user
To update a user using the
PATCH
method with the managedAPI
instance, you can use the updateUser
method in the ReqResAPI
class. This method is flexible and supports partial updates when you specify PATCH
as the HTTP method.Explanation:
- User identification:
userId
: Specify the ID of the user you want to partially update.- Partial update data:
updatedData
: An object containing only the fields you wish to update (e.g., job
in this example).- Method call:
Use
await managedAPI.updateUser(userId, updatedData, 'PATCH')
to send a partial update to the user data.- Response handling:
- Check
response.error
to handle errors during the request. - Access
response.data
to view the partially updated user details if the request is successful.
- Check
// Partially Update a User
// Define the user ID and partial data to update
const userId = 3; // Replace with the ID of the user to update
const updatedData = {
job: 'Lead Developer' // Only updating the job field
};
// Call the updateUser method with PATCH
const response = await managedAPI.updateUser(userId, updatedData, 'PATCH');
if (response.error) {
console.error('Error partially updating user:', response.error);
} else {
console.log('User Partially Updated Successfully:', response.data);
}
Sample Output:
If the user is updated successfully, the console might log:
User Partially Updated Successfully: {
name: "Jane Doe", // This remains unchanged
job: "Lead Developer",
updatedAt: "2024-12-03T12:34:56.789Z"
}
This approach ensures that only the specified fields in
updatedData
are updated while other user details remain intact. Using PATCH
is ideal for scenarios where you need to modify only part of the user's data.DELETE request: Remove a user
To remove a user using the
DELETE
method with the managedAPI
instance, you can use the deleteUser
method in the ReqResAPI
class. This method requires the userId
of the user you wish to delete.Explanation:
1. User identification:
userId
: Specify the ID of the user you want to delete.2. Method call:
Use
await managedAPI.deleteUser(userId)
to send a DELETE
request to remove the user.3. Response handling:
- Check
response.error
to identify any issues during the request. - If the request is successful, confirm the user was removed by logging a success message.
// Remove a User
// Define the user ID to delete
const userId = 4; // Replace with the ID of the user to delete
// Call the deleteUser method
const response = await managedAPI.deleteUser(userId);
if (response.error) {
console.error('Error removing user:', response.error);
} else {
console.log(`User with ID ${userId} removed successfully.`);
}
Sample output:
If the user is removed successfully, the console might log:
User with ID 4 removed successfully.
If there’s an error (e.g., the user does not exist), the error will be logged:
Error removing user: Unexpected response code: 404 - User not found
This approach ensures that you can dynamically delete any user by specifying their userId.
Conclusion
Congratulations! By following this tutorial series, you’ve learned how to connect anything with a REST API using ScriptRunner Connect. Here's a recap of the key takeaways:
1. Getting to grips with the basics of REST APIs
- You gained a solid understanding of REST APIs and how they allow disparate systems to communicate using standard HTTP methods.
2. REST APIs made easy with ScriptRunner Connect
- You explored how to automate REST API interactions with ScriptRunner Connect, creating scripts to perform
GET
,POST
,PUT
,PATCH
, andDELETE
operations. - You also learned how to enhance automation with scheduled triggers.
3. Building a managed API
- This final post demonstrated how to encapsulate API logic into a reusable and scalable TypeScript package, enabling portability and maintainability.
- You learned how to structure your project, implement robust API methods, and publish your managed API to npm.
What’s next?
For those looking to take their managed API implementation to the next level, consider enhancing the ReqResAPI class by transitioning the private
client
type from any to GenericAppApiCore
. This improvement involves:- Updating the imports to include:
//import { GenericAppApiCore } from "@managed-api/generic-core";
- Modifying the methods for GET, POST, PUT, PATCH, and DELETE to handle the stricter typing and return formats required by
GenericAppApi
Core.
This enhancement will improve type safety and ensure compatibility with more advanced automation workflows.
Let’s hear your feedback!
Have you completed the series and have ideas or questions about implementing a managed API or using
GenericAppApiCore
? We’d love to hear from you! Send us a message or reach out on social media to share your thoughts on this tutorial.Keep coding, keep automating, and keep connecting!
Published on 5 March 2025
Authors

Rafael Pinto Sperafico

Ambientia
Share this tutorial
Written by

Senior Software Developer
I’m part of the dynamic teams at Ambientia, working at the intersection of innovation and technology. With a passion for APIs, automation, and developer enablement, I’m excited to guide you through the fundamentals of REST APIs and how they can transform the way applications communicate and integrate by using ScriptRunner Connect.
Connect with Rafael on LinkedIn.

Ambientia is a Finnish technology consultancy, partners with organizations to create smart digital solutions. With a focus on collaboration, automation, and user-centric design, Ambientia brings cutting-edge technologies to life, helping companies across various industries to innovate and grow sustainably.
Related content
Discover more like this

Getting to grips with the basics of REST APIs (From REST consumer to API provider 1/3)
Welcome to the From REST consumer to API provider guest tutorial. In this three-part series, Rafael Pinto Sperafico from The Adaptavist Group partner, Ambientia, will guide you through the world of REST APIs.
by Rafael Pinto Sperafico
on 5 Mar 2025

REST APIs made easy with ScriptRunner (From REST consumer to API provider 2/3)
Welcome to the From REST consumer to API provider guest tutorial. In this three-part series, Rafael Pinto Sperafico from The Adaptavist Group partner, Ambientia, will guide you through the world of REST APIs. In this tutorial we’ll explore how to automate REST API calls using ScriptRunner Connect.
by Rafael Pinto Sperafico
on 5 Mar 2025