HeadlessOps
Scripting Guide

npm Packages

Install any npm package into your HeadlessOps integration's execution environment. Declare dependencies in integration.yaml using the packages field, or via the requirements field through the REST API or MCP. Packages are cached for zero-overhead reinstalls on repeat runs.

npm Packages

HeadlessOps supports installing third-party npm packages directly into your integration's execution environment. Dependencies are declared in the packages field of integration.yaml — no separate package.json file is needed or read.

Declaring Requirements

The simplest and most portable way to declare dependencies is directly in integration.yaml. Every time you push files to the platform, the packages array is automatically parsed and applied — keeping your YAML the single source of truth for both logic and dependencies:

name: data-pipeline
description: "Pulls records, processes with lodash, stores results"

packages:
  - zod@^3.23
  - dayjs@2.0
  - lodash@^4.17.21

When you push this YAML (via integration_files_push, integration_files_push_run, or deploy), the packages array is synced to the integration's requirements field automatically. No additional API call is required.

Via MCP

Use the integrations_update MCP tool to set package requirements directly, without modifying the YAML:

{
  "tool": "integrations_update",
  "arguments": {
    "name": "my-integration",
    "requirements": ["zod@^3.23", "dayjs@2.0", "lodash"]
  }
}

Via REST API

curl -X PUT https://app.headlessops.ai/api/integrations/my-integration \
  -H "Authorization: Bearer iter_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "requirements": ["zod@^3.23", "dayjs@2.0", "lodash"]
  }'

Requirements take effect on the next run after the update is applied.

Note: If both the packages field in integration.yaml and the requirements field are set via API, the YAML push always wins — the packages array overwrites the stored requirements on every push. Use the YAML field for everything that should be version-controlled, and the API only for ad-hoc overrides.

Supported Specifier Formats

FormatExampleBehaviour
LatestlodashInstalls the newest available version from npm
Pinneddayjs@2.0.0Exact version lock — recommended for production
Rangezod@^3.23Semver-compatible version range
Taglodash@latestSpecific dist-tag

You can freely mix pinned and unpinned specifiers in the same list. For example: ["zod@^3.23", "dayjs@2.0.0", "lodash"] is valid.

Package Caching

Dependencies are installed into a content-addressed environment identified by a hash of the sorted, normalised requirements list:

/pip-cache/venvs/{hash-of-sorted-requirements}/

How caching works:

  • If an identical requirements list was installed before, the cached modules are reused — no reinstall overhead.
  • Adding, removing, or changing any single requirement creates a new environment with a clean install.
  • Old environments are retained until the platform's cache pruning process removes them.

First-time installs typically complete in 2–15 seconds depending on package size and dependency depth. Subsequent runs with the same requirements add zero startup overhead.

Security Restrictions

The following specifier formats are blocked to prevent security vulnerabilities:

Blocked FormatExampleReason
Git URLsgit+https://github.com/org/repoArbitrary code execution risk
Direct URLs / tarballshttps://files.example.com/pkg.tgzUnverified package source
Local pathsfile:../local-pkgFilesystem access
Shell metacharacterslodash; rm -rf /Command injection prevention

Attempting to use a blocked specifier returns a 400 Bad Request error with a description of the violation.

Only packages available on the public npm registry (https://registry.npmjs.org) are supported. Private registries are not currently available.

Using Installed Packages in Steps

Import packages normally at the top of your step file. HeadlessOps activates the integration's environment before executing any step:

import _ from "lodash";
import dayjs from "dayjs";
import { z } from "zod";

export async function run(input: Record<string, any>, ctx: StepContext) {
  // lodash, dayjs, and zod are available
  const sorted = _.sortBy(input.items, "priority");
  ctx.log("sorted items", { count: sorted.length });
  return { sorted };
}

Example: HTTP Client with Schema Validation

This example uses the built-in ctx.http for async HTTP requests and zod for response validation:

import { z } from "zod";

const ContactSchema = z.object({
  id: z.string(),
  email: z.string().email(),
  name: z.string(),
});

export async function run(input: Record<string, any>, ctx: StepContext) {
  const apiKey = ctx.credentials.CRM_API_KEY;

  const response = await ctx.http.get(
    `https://api.crm.example.com/contacts/${input.contact_id}`,
    {
      headers: { Authorization: `Bearer ${apiKey}` },
      timeout: 25000,
    }
  );
  if (!response.ok) {
    throw new Error(`CRM API returned ${response.status}`);
  }

  const raw = await response.json();
  const contact = ContactSchema.parse(raw);
  ctx.log("fetched contact", { id: contact.id, email: contact.email });
  return contact;
}

Declare the dependencies in integration.yaml:

packages:
  - zod@^3.23

Frequently Asked Questions

Can I use a package.json file? No. The platform does not read package.json from the integration package. Declare all dependencies using the packages field in integration.yaml, or via the requirements field through the REST API or MCP.

Are private npm registries supported? Not currently. Only packages available on the public npm registry are supported.

Can I use packages with native extensions (e.g. sharp, bcrypt)? Packages that require native compilation must be available as pre-built binaries for the platform's Node.js runtime on Linux.

What Node.js version does the runtime use? The platform uses Node.js 24. Ensure your packages are compatible before declaring them as requirements.

How long does installation take? First-time installs typically complete in 2–15 seconds. Subsequent runs with identical requirements reuse the cached environment and add zero overhead.

What happens if I push integration.yaml with a packages field but also set requirements via the API? The YAML push always wins. Every time you push files containing a packages field, it overwrites the stored requirements. For version-controlled integrations, keep dependencies in the YAML; use the API only for temporary overrides.