Skip to main content

Prerequisites

Before you begin, make sure you have:
  • Node.js 20 or later installed
  • A Limrun API key (sign up at lim.run)
  • Basic familiarity with TypeScript or JavaScript

Installation

Install the Limrun SDK using your preferred package manager:
npm install @limrun/api

Set Up Authentication

The SDK requires an API key to authenticate requests. Set it as an environment variable:
export LIM_API_KEY="your-api-key-here"
The client automatically reads LIM_API_KEY from your environment. Alternatively, pass it directly when creating the client:
const limrun = new Limrun({ apiKey: 'your-api-key-here' });
Never commit your API key to version control. Use environment variables or secure secret management.

Your First iOS Instance

1

Create a new file

Create a file called quickstart.ts in your project:
import Limrun, { Ios } from '@limrun/api';

const limrun = new Limrun({
  apiKey: process.env['LIM_API_KEY'],
});
2

Provision an iOS instance

Create an iOS simulator instance. The wait: true option ensures the request doesn’t return until the instance is ready:
const instance = await limrun.iosInstances.create({
  wait: true,
  reuseIfExists: true,  // Reuse if already exists
  metadata: {
    labels: {
      name: 'quickstart-example',
    },
  },
});

console.log(`Instance created: ${instance.metadata.id}`);
console.log(`State: ${instance.status.state}`);
The reuseIfExists flag returns an existing instance with matching labels instead of creating a new one. This is useful for development to avoid creating duplicate instances.
3

Connect to the instance

Use the WebSocket API to connect and control the device:
if (!instance.status.apiUrl) {
  throw new Error('API URL is missing');
}

const client = await Ios.createInstanceClient({
  apiUrl: instance.status.apiUrl,
  token: instance.status.token,
  logLevel: 'info',
});

console.log('Connected to iOS instance');
4

Automate the device

Now you can interact with the iOS simulator. Let’s take a screenshot and launch Safari:
// Take a screenshot
const screenshot = await client.screenshot();
console.log(`Screenshot: ${screenshot.width}x${screenshot.height}`);

// Save the screenshot
import fs from 'fs';
fs.writeFileSync(
  'screenshot.jpg',
  Buffer.from(screenshot.base64, 'base64')
);

// Launch Safari
await client.launchApp('com.apple.mobilesafari');
console.log('Launched Safari');

// Open a URL
await client.openUrl('https://www.example.com');
console.log('Opened example.com');

// Tap at coordinates
await client.tap(200, 400);
console.log('Tapped at (200, 400)');
5

Clean up

Always disconnect when you’re done:
client.disconnect();
console.log('Disconnected');
Instances automatically terminate after 3 minutes of inactivity by default. You can customize this with the inactivityTimeout spec option.

Complete Example

Here’s the full code from the quickstart:
quickstart.ts
import Limrun, { Ios } from '@limrun/api';
import fs from 'fs';

const limrun = new Limrun({
  apiKey: process.env['LIM_API_KEY'],
});

// Create iOS instance
const instance = await limrun.iosInstances.create({
  wait: true,
  reuseIfExists: true,
  metadata: {
    labels: { name: 'quickstart-example' },
  },
});

console.log(`Instance created: ${instance.metadata.id}`);

// Connect via WebSocket
const client = await Ios.createInstanceClient({
  apiUrl: instance.status.apiUrl!,
  token: instance.status.token,
  logLevel: 'info',
});

try {
  // Take a screenshot
  const screenshot = await client.screenshot();
  fs.writeFileSync('screenshot.jpg', Buffer.from(screenshot.base64, 'base64'));
  console.log(`Screenshot saved: ${screenshot.width}x${screenshot.height}`);

  // Launch Safari and navigate
  await client.launchApp('com.apple.mobilesafari');
  await client.openUrl('https://www.example.com');
  console.log('Opened example.com in Safari');

  // Interact with the page
  await client.tap(200, 400);
  console.log('Tapped at (200, 400)');
} finally {
  client.disconnect();
  console.log('Disconnected');
}

Your First Android Instance

Creating an Android instance follows a similar pattern:
import Limrun, { createInstanceClient } from '@limrun/api';

const limrun = new Limrun({
  apiKey: process.env['LIM_API_KEY'],
});

// Create Android instance
const androidInstance = await limrun.androidInstances.create({
  wait: true,
});

console.log(`Instance ${androidInstance.metadata.id} created`);

// Connect via WebSocket
const client = await createInstanceClient({
  adbUrl: androidInstance.status.adbWebSocketUrl!,
  endpointUrl: androidInstance.status.endpointWebSocketUrl!,
  token: androidInstance.status.token,
});

// Take a screenshot
const screenshot = await client.screenshot();
console.log(`Screenshot: ${screenshot.width}x${screenshot.height}`);

// Start ADB tunnel for advanced control
const { address, close } = await client.startAdbTunnel();
console.log(`ADB available at ${address.address}:${address.port}`);

// Clean up
close();
client.disconnect();

Next Steps

API Reference

Explore all available methods and types

iOS Instances Guide

Learn about iOS instance management and automation

Android Instances Guide

Discover ADB tunneling and Android automation

Error Handling

Handle errors gracefully with structured error types

Common Patterns

Reusing Instances

Avoid creating duplicate instances during development:
const instance = await limrun.iosInstances.create({
  wait: true,
  reuseIfExists: true,  // Returns existing instance if labels match
  metadata: {
    labels: {
      environment: 'dev',
      user: 'alice',
    },
  },
});

Installing Apps

Install apps from URLs during instance creation:
const instance = await limrun.iosInstances.create({
  wait: true,
  spec: {
    initialAssets: [
      {
        kind: 'App',
        source: 'URL',
        url: 'https://example.com/MyApp.app.zip',
        launchMode: 'ForegroundIfRunning',
      },
    ],
  },
});

Element-Based Interactions

Use accessibility selectors instead of hard-coded coordinates:
// Tap a button by its accessibility label
await client.tapElement({ elementType: 'Button', label: 'Submit' });

// Set text field value (faster than typing)
await client.setElementValue('[email protected]', {
  elementType: 'TextField',
  labelContains: 'Email',
});

Listing Installed Apps

const apps = await client.listApps();
apps.forEach((app) => {
  console.log(`${app.name} (${app.bundleId})`);
});
Check out the examples directory in the GitHub repository for complete working examples including Playwright, Appium, and WebSocket integrations.

Build docs developers (and LLMs) love