The Local API Development Revolution
Here's the thing about cloud development that nobody talks about: you shouldn't need an internet connection to understand your code. ScriptRunner Connect's External Coding lets you build local API clients that make real HTTP requests to Atlassian Cloud – all while maintaining the accessible development experience you've been building throughout this series.
Why This Changes Everything (Especially for Accessibility)
Building on the foundation from our previous posts, local API development provides:
Screen Reader Friendly Debugging: Your screen reader can properly navigate through:
- API response data formatted in your IDE (the same WebStorm setup from Series #1: Your IDE, Your Rules - Setting Up Accessible Development)
- Clear error messages in accessible terminal output
- Structured logging that makes sense when read aloud
- Code completion that actually helps instead of frustrates
Consistent Development Experience:
- Your IDE's debugging tools work with real API data
- Keyboard shortcuts for stepping through API calls
- Familiar breakpoint and variable inspection interfaces (using the WebStorm configuration from Series #1: Your IDE, Your Rules - Setting Up Accessible Development)
- No context switching between browser dev tools and your IDE
Integration with Your Testing Workflow:
- Combine real API testing with the Jest setup from Series #2: Testing Without Tears - Mocking APIs Like a Pro
- Use runtime mocks for unit tests, real APIs for integration tests
- Maintain the same accessible testing patterns you've already established
The Architecture That Actually Makes Sense
ScriptRunner Connect provides a brilliant pattern for local API development. Instead of trying to mock everything (like we did for unit testing in Series #2: Testing Without Tears - Mocking APIs Like a Pro), you create local HTTP clients that extend the same managed API cores used in production. It's like having a direct phone line to Atlassian Cloud from your IDE.
The Magic Files
Building on the project structure you downloaded in Series #1: Your IDE, Your Rules - Setting Up Accessible Development, here's what makes local API development possible:
node/ ├── client/jira/cloud/JiraCloudClient.ts # Your local HTTP client (new!) ├── apiRegistry.ts # Central API management (new!) ├── external/coding/ # Local execution scripts (new!) ├── runtimeMocks.ts # Runtime mocks (from Series #2) └── tests/ # Jest tests (from Series #2)
Each file has a specific job, and together they create an environment where you can develop and debug with real data while maintaining the testing capabilities you built in Series #2: Testing Without Tears - Mocking APIs Like a Pro.
Building Your First Local API Client
Let's create a Jira Cloud client that you can actually debug and understand. This builds on the development environment you set up in Series #1: Your IDE, Your Rules - Setting Up Accessible Development and complements the testing framework from Series #2: Testing Without Tears - Mocking APIs Like a Pro.
The Foundation - JiraCloudClient.ts
// node/client/jira/cloud/JiraCloudClient.tsimport { PlatformImplementation, Request, Response as CommonsResponse, Headers as CommonsHeaders, BaseApiCore,} from '@managed-api/commons-core';import { JiraCloudApiCore } from '@managed-api/jira-cloud-v3-core';/** * Local implementation of Jira Cloud API client for development and testing. * * Extends JiraCloudApiCore to provide HTTP-based communication with Jira Cloud * using Basic Authentication. This client enables local development by making * direct API calls to Atlassian Cloud services. * * @example * ```typescript * const client = new JiraCloudClient( * 'your-email@company.com', * 'your-api-token' * ); * ``` */export class JiraCloudClient extends JiraCloudApiCore { constructor( private baseUrl: string, private email: string, private apiToken: string, ) { super(); } protected getPlatformImplementation(): PlatformImplementation { return { buffer: { encode: (input) => Buffer.from(input, 'utf-8'), decode: (input) => Buffer.from(input).toString('utf-8'), }, performHttpCall: (request) => this.performHttpCall(request), }; } private async performHttpCall(request: Request): Promise<CommonsResponse> { const requestUrl = `${this.baseUrl}${request.url}`; // Substitute base URL request.headers.set('Authorization', `Basic ${btoa(`${this.email}:${this.apiToken}`)}`); // Substitute auth token const requestHeaders = new Headers(); request.headers.forEach((value, key) => requestHeaders.append(key, value)); const response = await fetch(requestUrl, { method: request.method, headers: requestHeaders, body: request.method.toLowerCase() !== 'get' ? Buffer.from((await request.arrayBuffer()) ?? new ArrayBuffer(0)) : undefined, }); const responseHeaders = new CommonsHeaders(); response.headers.forEach((value, key) => responseHeaders.append(key, value)); const body = await response.arrayBuffer(); return super.buildResponse(response.url, response.status, response.statusText, responseHeaders, body); }}The Registry - apiRegistry.ts
We are going to comment references to the mock JiraCloudClientMock.
// node/apiRegistry.tsimport { config } from 'dotenv';import { BaseApiCore } from '@managed-api/commons-core';import { JiraCloudClient } from "./client/jira/cloud/JiraCloudClient";//import { JiraCloudClientMock } from './client/jira/cloud/JiraCloudClientMock';// Load environment variables from .env fileconfig();export type ApiRegistry = Record<string, BaseApiCore>;export const API_REGISTRY: ApiRegistry = { // Declare here locally implemented API connections // Client without making real HTTP requests //'jira/cloud': new JiraCloudClientMock(), // Client making real HTTP requests 'jira/cloud': new JiraCloudClient( 'your-email@company.com', 'your-api-token' ),};Security is important, but it shouldn't be complicated. You should consider extracting parameters such as baseURL, email and api-token to Environment Variables.
Creating API Integration Scripts
Now let's build a script that uses your local API client. This is where the magic happens – you're making real API calls but with full local debugging capabilities, all within the accessible WebStorm environment you configured in Series #1: Your IDE, Your Rules - Setting Up Accessible Development.
The Projects Integration
// scripts/cloud/atlassian/jira/Projects.tsimport { GetProjectsResponseOK } from '@managed-api/jira-cloud-v3-core/types/project';import JiraCloud from '../../../api/jira/cloud';/** * Recursively fetches all projects from Jira Cloud with pagination support. * * @param pageNumber - The page number to fetch (starts from 1) * @param projects - Accumulator array for collected projects across all pages * @returns Promise that resolves to an array of all projects */export async function getProjects(pageNumber = 1, projects: GetProjectsResponseOK['values'] = []): Promise<GetProjectsResponseOK['values']> { try { const response = await JiraCloud.Project.getProjects() if(response.values) { console.log(`Found ${response.values.length} projects on page ${pageNumber}`); projects.push(...response.values) } if(!response.isLast) { console.log(`More pages available, fetching page ${pageNumber + 1}...`); return await getProjects(pageNumber + 1, projects) } console.log(`Total projects retrieved: ${projects.length}`); return projects } catch (error) { console.error(`Error fetching projects on page #${pageNumber}:`, error.message); throw(error); }}Local Execution Magic
The real superpower is being able to run and debug your scripts locally with real data, using the same accessible terminal environment you've been developing throughout this series:
Local Execution Script
// node/external/coding/OnJiraCloudProjectCreated.tsimport OnJiraCloudProjectCreated from "../../../scripts/OnJiraCloudProjectCreated";import { ProjectCreatedEvent } from "@sr-connect/jira-cloud/types/project";/** * Local execution script for testing project creation events. */(async () => { await OnJiraCloudProjectCreated({} as ProjectCreatedEvent, { availableMemory: 400, invocationId: 'INVOCATION_ID', triggerType: 'MANUAL', rootTriggerType: 'MANUAL', startTime: Date.now(), timeout: 900000, environment: { uid: 'ENVIRONMENT_UID', name: 'Default', vars: { // Environment variables defined in ../../../ev.ts }, } })})().catch(console.error);Advanced Testing with Real APIs
Now comes the really exciting part – combining the testing framework from Series #2: Testing Without Tears - Mocking APIs Like a Pro with real API data while maintaining accessibility:
Integration Testing That Actually Works
// node/tests/cloud/atlassian/jira/Projects.test.tsimport '../../../../runtimeMocks';import { jest } from '@jest/globals';import { getProjects } from "../../../../../scripts/cloud/atlassian/jira/Projects";// Array for intercepted console.log messageslet interceptedConsoleLogs: string[] = [];// Function for getting mocked Context objectexport function getContext(ev: EV): Context<EV> { return { availableMemory: 400, invocationId: 'INVOCATION_ID', triggerType: 'MANUAL', rootTriggerType: 'MANUAL', startTime: Date.now(), timeout: 900000, environment: { uid: 'ENVIRONMENT_UID', name: 'Default', vars: ev, }, };}beforeAll(() => { // Mock all console.log methods to intercept messages and store them jest.spyOn(console, 'log').mockImplementation((...args) => interceptedConsoleLogs.push(args.join(' ')));});// npm run testdescribe('Projects API Integration Tests', () => { test('Should fetch real projects from Jira Cloud', async () => { const projects = await getProjects(); // Test with real data expect(projects).toBeDefined(); expect(Array.isArray(projects)).toBe(true); expect(projects.length).toBeGreaterThan(0); // Test project structure if (projects.length > 0) { const firstProject = projects[0]; expect(firstProject).toHaveProperty('id'); expect(firstProject).toHaveProperty('key'); expect(firstProject).toHaveProperty('name'); } });});afterAll(() => { jest.clearAllMocks();});The Accessibility-First Development Workflow
Here's a development workflow that leverages your accessible local environment, building on everything you've set up in this series:
Develop with Real Data
Install `tsx` globally to execute TypeScript files directly:
# Install tsx globally for direct TypeScript executionnpm install -g tsx# Run your script locally with real API callstsx node/external/coding/OnJiraCloudProjectCreated.tx
Deploy via SFTP
Use the SFTP configuration from Series #1: Your IDE, Your Rules - Setting Up Accessible Development to deploy your tested scripts:
- Upload via WebStorm's deployment tools
- Monitor upload progress in accessible Event Log
- Verify deployment in ScriptRunner Connect's Scripts section
What We've Accomplished
API mastery unlocked! You've just graduated from "guessing what the API returns" to "debugging with real data like a wizard." Your local HTTP clients are chatting with Atlassian Cloud like old friends, your screen reader is announcing successful API calls with satisfaction, and you're debugging with the confidence of someone who actually knows what's happening.
Congratulations, you've essentially built a secure tunnel between your IDE and Atlassian's servers. It's like having a direct hotline to Jira and Confluence, except instead of calling to complain about bugs, you're calling to understand them, fix them, and maybe even prevent them. Your future self will thank you when 3 AM debugging sessions become pleasant afternoon code reviews.
The grand finale awaits: ScriptRunner Connect External Coding Series #4: AI-Powered Development - Your New Coding Superpower (That Actually Understands Accessibility) is where we bring in AI assistance that's so well-trained on accessibility, it probably writes better JSDoc comments than most humans. Get ready for code generation that actually makes sense when your screen reader announces it.
Ready to supercharge your development with AI assistance? Check out our final post in this series.
Previous posts in this series:
- ScriptRunner Connect External Coding Series #1: Your IDE, Your Rules - Setting Up Accessible Development
- ScriptRunner Connect External Coding Series #2: Testing Without Tears - Mocking APIs Like a Pro
About the Author and Development Team
Rafael Pinto Sperafico, Senior Atlassian Consultant and technical architect behind the ScriptRunner Connect External Coding Series, brings years of hands-on experience helping organisations transform their Atlassian automation development workflows. As part of Ambientia's expert Atlassian team, Rafael Pinto Sperafico specialises in accessibility-first development practices, enterprise-grade ScriptRunner Connect implementations, and cutting-edge AI-assisted development methodologies. This series emerged from real-world client work where teams needed professional, accessible development environments that didn't force them to abandon their familiar tools or compromise on functionality.
The comprehensive insights shared throughout this series reflect the collective expertise of Ambientia's Atlassian Consultants Team – experienced professionals passionate about making powerful development tools accessible to all developers. Our team combines technical expertise in ScriptRunner Connect with a deep understanding of accessibility requirements, helping development teams explore professional local development environments that integrate with cloud execution. We specialise in the methodologies covered in this series: WebStorm SFTP configuration, comprehensive testing frameworks, local API development, and AI-powered automation workflows that respect accessibility standards.
Ready to transform your ScriptRunner Connect development experience? Whether you're struggling with browser-based development limitations, need to implement accessibility-compliant automation workflows, or want to leverage AI assistance like Claude Code while maintaining professional development standards, Ambientia's Atlassian experts are here to help. We offer tailored consultancy for External Coding implementations, accessibility audits for existing automations, and comprehensive training on the workflows covered in this series. Contact Ambientia today to discuss how our proven External Coding methodologies can accelerate your team's productivity while ensuring every developer – regardless of their assistive technology needs – can contribute effectively to your Atlassian automation projects.
Subscribe to our content
Do you want our content in your email? Among other things, we offer information on service management and customer experience development
More related
Atlassian
ScriptRunner Connect External Coding Series #1: Your IDE, Your Rules
Set up ScriptRunner Connect External Coding with your local IDE for accessible, professional Atlassian automation development.
Read more
Published 25.9.2025 | Updated 26.9.2025
Atlassian
ScriptRunner Connect External Coding Series #2: Testing Without Tears
Learn how to test ScriptRunner Connect scripts locally with Jest and mock APIs—accessible, reliable, and no more debugging nightmares.
Read more
Published 25.9.2025 | Updated 26.9.2025
