npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

autotask-rest-api-types

v0.1.1

Published

Comprehensive, generated TypeScript types for the Datto/Kaseya Autotask PSA REST API. Strongly-typed entities, query filters, responses, and a typed client surface for the apigrate/autotask-restapi connector. Built for Next.js and TS projects.

Downloads

267

Readme

autotask-rest-api-types

TypeScript types for the Datto/Kaseya Autotask PSA REST API, generated from Autotask's official Swagger 2.0 spec. Entity interfaces, typed query filters and response envelopes, a typed client surface, and a machine-readable catalog of every collection — for Next.js/TypeScript integrations, tooling, and codegen.

  • 223 entity interfaces (Companies, Tickets, Contacts, Contracts, Configuration Items, Projects, Time Entries, attachments, …) with per-field JSDoc.
  • 221 REST collections mapped to their entity in AutotaskEntities.
  • Query filters — all 13 query operators plus grouped and/or, includeFields, a fluent filter DSL, and cursor pagination helpers.
  • Response envelopes{ items, pageDetails }, { item }, { itemId }, plus entity-information / live picklist metadata shapes.
  • Two client surfaces — typing for the @apigrate/autotask-restapi connector, and a provider-agnostic AutotaskTypedClient you implement over fetch.
  • Runtime catalogAUTOTASK_COLLECTIONS describes every collection (operations, UDF support, parent collections).
  • Webhook helpers — typed delivery parsing, HMAC verification, registration plan builders, and an optional router.
  • Instance enrichmentnpx autotask-enrich (read-only) captures your instance's live picklist values, required/read-only flags, and reference targets into your project, plus strict picklist type aliases (TicketPriorityValue = 1 | 2 | 3 | 4). Instance data is never bundled.
  • Type-only by default — no runtime dependencies; tree-shakeable; ESM with full .d.ts.

Unofficial / community-maintained. "Autotask" is a trademark of Datto/Kaseya. Generated from the public API spec; not affiliated with or endorsed by Datto or Kaseya.


Install

pnpm add autotask-rest-api-types
# or: npm i autotask-rest-api-types   /   yarn add autotask-rest-api-types

Install it as a normal dependency — the package ships a few runtime helpers (filters, collectAll/iterateAll, getUdf/toUdfArray, writtenId, requireField, isPresent, isAutotaskError, and the AUTOTASK_COLLECTIONS catalog). If you import only types (import type …), a dev dependency (-D) is fine.

The package has no runtime dependencies of its own. If you also want a transport, the examples below use the official connector:

pnpm add @apigrate/autotask-restapi

Quick start — with the apigrate connector

Cast the connector instance to AutotaskApi for end-to-end typing:

import { AutotaskRestApi } from "@apigrate/autotask-restapi";
import type { AutotaskApi, Company } from "autotask-rest-api-types";
import { filters } from "autotask-rest-api-types";

const autotask = new AutotaskRestApi(
  process.env.AUTOTASK_USER!,        // API user (UserName header)
  process.env.AUTOTASK_SECRET!,      // secret (Secret header)
  process.env.AUTOTASK_INTEGRATION_CODE!, // tracking id (ApiIntegrationCode header)
) as unknown as AutotaskApi;

const f = filters<Company>();
const { items, pageDetails } = await autotask.Companies.query({
  maxRecords: 50,
  includeFields: ["id", "companyName", "isActive"],
  filter: [f.eq("isActive", true), f.beginsWith("companyName", "A")],
});

items.forEach((c: Company) => console.log(c.id, c.companyName));

filter fields and includeFields autocomplete from the entity; items is Company[].


Quick start — provider-agnostic (fetch, Next.js route handler, etc.)

Implement the small AutotaskTypedClient interface over any transport. The entity type is inferred from the collection name on every call:

import type { AutotaskTypedClient } from "autotask-rest-api-types";

// (sketch) your own thin wrapper around fetch + zone detection
const client: AutotaskTypedClient = createMyClient();

// `tickets` is typed as Ticket[] — inferred purely from the string "Tickets".
const { items: tickets } = await client.query("Tickets", {
  filter: [{ op: "eq", field: "status", value: 1 }],
});

See skills/autotask-rest-api for the AI-assistant guide and a complete fetch-based client (header auth + zone detection), and examples/nextjs for a runnable Next.js Ticket Console (simulated without credentials, live when present).

Error handling

Failed requests return an { errors: string[] } envelope (HTTP 400/500) — invalid picklist values, maxRecords > 500, missing required fields, etc. Type and narrow it with AutotaskErrorResponse / isAutotaskError:

import { isAutotaskError } from "autotask-rest-api-types";

const res = await fetch(url, { method: "POST", headers, body });
if (!res.ok) {
  const body = await res.json();
  if (isAutotaskError(body)) console.error(body.errors); // string[]
}

Connector vs fetch. The apigrate connector throws on a failed request instead of returning the envelope — the call rejects with an AutotaskApiError carrying .status and .details (the { errors } body). Wrap those calls in try/catch; the isAutotaskError check above is for the raw fetch / AutotaskTypedClient path, which returns the body.


Webhooks

Webhook helpers are available from the root package and from the tree-shakeable subpath:

import {
  AUTOTASK_SIGNATURE_HEADER,
  isDeliveryFor,
  isUpdateDelivery,
  parseWebhookDelivery,
  verifyAutotaskSignature,
} from "autotask-rest-api-types/webhooks";

Receive and verify a delivery

Read the raw body before parsing JSON. Autotask signs the raw request body with the webhook secret:

export async function POST(req: Request) {
  const secret = process.env.AUTOTASK_WEBHOOK_SECRET;
  if (!secret) return new Response("Missing AUTOTASK_WEBHOOK_SECRET", { status: 500 });

  const raw = await req.text();
  const signature = req.headers.get(AUTOTASK_SIGNATURE_HEADER);

  // If real callouts return 401, retry with `{ escapeBody: true }`.
  // That escape pass is reverse-engineered and undocumented, so keep it opt-in.
  const ok = await verifyAutotaskSignature(raw, secret, signature);
  if (!ok) return new Response("Invalid signature", { status: 401 });

  const delivery = parseWebhookDelivery(JSON.parse(raw));
  if (isDeliveryFor(delivery, "Tickets") && isUpdateDelivery(delivery)) {
    const title = delivery.Fields.title; // string | null | undefined
    // Update callouts contain subscribed/changed fields only.
    void title;
  }

  return Response.json({ ok: true });
}

parseWebhookDelivery validates Action, accepts Autotask's legacy EntityType names such as Account, and normalizes them to package collection names such as Companies. The field payload is deliberately permissive because a raw live callout still needs to confirm exact Fields casing, UDF nesting, and whether delete/deactivation deliveries omit Fields or send {}.

Plan webhook registration

buildWebhookRegistrationPlan creates a parent-first plan with child field, UDF-field, and excluded-resource rows. If you already have numeric field ids, the executor creates the parent, reads the id with writtenId, and injects webhookID into child bodies:

import {
  buildWebhookRegistrationPlan,
  executeWebhookRegistrationPlan,
} from "autotask-rest-api-types/webhooks";

const plan = buildWebhookRegistrationPlan("Ticket", {
  webhook: {
    name: "Ticket updates",
    webhookUrl: "https://example.com/api/autotask",
    isSubscribedToUpdateEvents: true,
  },
  fields: [{ fieldID: 12, subscribed: true }],
  excludedResourceIDs: [apiUserResourceId],
});

await executeWebhookRegistrationPlan(plan, async (step, body, parentId) => {
  // Call your client here. Child steps receive `parentId` and a body containing `webhookID`.
  return createWebhookRow(step.collection, body, parentId);
});

If you start from field names, resolve them from the live webhook child collection metadata, not from Tickets.fieldInfo() or the parent webhook route. The flow is:

  1. Create or find the parent webhook and normalize its id with writtenId(res).
  2. Query TicketWebhookFields/entityInformation/fields and read the fieldID picklist. For UDFs, query TicketWebhookUdfFields/entityInformation/fields and read udfFieldID.
  3. Build nameToId, call resolveFieldIds(["title", "status"], nameToId), then create the child rows with { ...step.body, webhookID: webhookId }.

Webhook endpoints should acknowledge quickly with a 2xx response, dedupe on Guid, and keep retry/queue/persistence concerns in your app. A successful registration can still fail to fire if isReady is false or the owner resource lacks permission; sustained failures can deactivate a webhook.


Entities & the collection registry

Import any entity interface directly:

import type { Ticket, Contact, ConfigurationItem, ContractService } from "autotask-rest-api-types";

Map a collection name to its entity type generically:

import type { AutotaskEntities, EntityName, EntityOf } from "autotask-rest-api-types";

type T = AutotaskEntities["Tickets"];     // Ticket
type C = EntityOf<"Companies">;           // Company
function load<K extends EntityName>(name: K): Promise<EntityOf<K>[]> { /* ... */ }

Field typing conventions

Generated from the spec; the type mapping is:

| Swagger | TypeScript | Notes | |---|---|---| | integer (int32 / int64) | number | Autotask ids stay within JS safe-integer range | | number (double) | number | | | string | string | | | string (date-time) | string | UTC, no Z/offset, e.g. 2024-01-31T15:04:05.307; append "Z" to parse | | string (byte) | string | base64 (attachment data) | | boolean | boolean | | | userDefinedFields | UserDefinedField[] | name/value pairs (see below) |

Every field is optional and nullable (field?: T | null). This is deliberate and reflects the API's real behavior: Autotask returns null for empty fields and omits any field you leave out of includeFields. id is readonly id?: number (server-assigned, never null on a fetched record). Read-only fields (server-managed, e.g. createDate) carry the readonly modifier and JSDoc.

Working with nullable fields

Helpers cut down on null-narrowing boilerplate:

import { requireField, isPresent } from "autotask-rest-api-types";
import type { Loaded, WithId, Company } from "autotask-rest-api-types";

// get() resolves to bare `null` on a 404 (the apigrate connector returns the
// API body verbatim), so null-check the result before destructuring `item`.
const res = await autotask.Companies.get(1);
if (res?.item) {
  const name = requireField(res.item, "companyName"); // string — throws if null/absent
  const loaded = res.item as Loaded<Company>;          // every field non-null (assertion of intent)
  loaded.companyName.trim();
}

const active = companies.filter((c) => isPresent(c.companyName)); // typed narrowing in filter
const withId: WithId<Company> = { id: 1, companyName: "Acme" };    // id guaranteed present

Legacy business-object names

Autotask's REST collection names differ from its older SOAP/business-object names: Company = Account, ConfigurationItem = InstalledProduct, BillingCode = AllocationCode, etc. Each entity's legacy name is in AUTOTASK_COLLECTIONS[name].entity, and ENTITY_TO_COLLECTION resolves the reverse:

import { ENTITY_TO_COLLECTION } from "autotask-rest-api-types";
ENTITY_TO_COLLECTION["Account"];           // "Companies"
ENTITY_TO_COLLECTION["InstalledProduct"];  // "ConfigurationItems"

Querying

Queries follow the Swagger QueryModel: { maxRecords?, includeFields?, filter[] }. Top-level filter entries are combined with AND; use explicit and / or groups for anything else.

Operators

| Kind | Operators | |---|---| | Comparison | eq, noteq, gt, gte, lt, lte, beginsWith, endsWith, contains | | Existence (no value) | exist, notExist | | Set (array value) | in, notIn | | Grouping (nested items) | and, or |

As plain objects

import type { AutotaskQuery, Ticket } from "autotask-rest-api-types";

const q: AutotaskQuery<Ticket> = {
  maxRecords: 100,
  includeFields: ["id", "ticketNumber", "title", "status"],
  filter: [
    { op: "eq", field: "companyID", value: 123 },
    { op: "in", field: "status", value: [1, 5, 8] },
    { op: "or", items: [
      { op: "gte", field: "createDate", value: "2024-01-01T00:00:00Z" },
      { op: "exist", field: "lastActivityDate" },
    ] },
  ],
};

With the fluent DSL

import { filters } from "autotask-rest-api-types";
import type { Ticket } from "autotask-rest-api-types";

const f = filters<Ticket>();
const q = {
  filter: [
    f.eq("companyID", 123),
    f.in("status", [1, 5, 8]),
    f.or(f.gte("createDate", "2024-01-01T00:00:00Z"), f.exist("lastActivityDate")),
    f.udf("CustomerRef", "eq", "ACME-001"), // user-defined field
  ],
} satisfies AutotaskQuery<Ticket>;

Pagination

Autotask returns up to 500 records per page (MAX_PAGE_SIZE) with a pageDetails block. To pull an entire result set, use the built-in id-cursor helpers (no reliance on nextPageUrl):

import { collectAll, iterateAll } from "autotask-rest-api-types";

// Buffer everything:
const all = await collectAll((q) => autotask.Tickets.query(q), {
  filter: [{ op: "eq", field: "companyID", value: 123 }],
});

// Or stream page-by-page:
for await (const ticket of iterateAll((q) => autotask.Tickets.query(q), baseQuery)) {
  // ...
}

Create / Update / Delete

import type { CreateModel, UpdateModel, Ticket } from "autotask-rest-api-types";
import { writtenId } from "autotask-rest-api-types";

// Create — omit `id` (server-assigned). Returns HTTP 200 with { itemId }.
const create: CreateModel<Ticket> = { companyID: 1, title: "Server down", status: 1, priority: 2 };
const res = await autotask.Tickets.create(create);
const id = writtenId(res); // normalize itemId to a number

// Update (PATCH, partial) — only the fields you send change. `id` is required.
const patch: UpdateModel<Ticket> = { id, status: 5 };
await autotask.Tickets.update(patch);

// Delete — only on entities that allow it. Tickets are NOT deletable via the API
// (AUTOTASK_COLLECTIONS.Tickets.canDelete === false); a TimeEntry, for example, is:
await autotask.TimeEntries.delete(someTimeEntryId);

CreateInput<"Tickets"> / UpdateInput<"Tickets"> are convenience aliases keyed by collection name.

PATCH vs PUT. update() is a PATCH (sparse): only the fields you send change. replace() is a PUT (full replace): every writable field you omit is set to null/default. The types enforce this — replace() takes a ReplaceModel<T> that requires every writable field (read-only excluded), so a partial like { id, title } won't compile; build it from a fully-loaded record (replace({ ...loaded, title })). Prefer update() unless you deliberately want a full replace. Create, update, and replace all return HTTP 200 with { itemId } (Autotask does not use 201).

Many entities can't be deleted via the API (e.g. Companies, Tickets), and child entities like *Notes are created/updated under their parent (POST /Companies/{parentId}/Notes). The runtime catalog records this — check AUTOTASK_COLLECTIONS[name].canDelete and .parentWriteOnly / .parents before assuming an operation exists.


User-defined fields (UDFs)

UDFs arrive as an array of { name, value } pairs (values are always strings on the wire):

import { getUdf, toUdfArray } from "autotask-rest-api-types";

const ref = getUdf(ticket.userDefinedFields, "CustomerRef"); // string | null

await autotask.Tickets.update({
  id: 123,
  userDefinedFields: toUdfArray({ CustomerRef: "ACME-001", SlaTier: 2 }),
});

Live metadata & picklists

Picklist option values are instance-specific and therefore not in the static spec — fetch them at runtime via the entity-information endpoints, which are fully typed here:

import type { FieldInformationResult, PickListValue } from "autotask-rest-api-types";

const { fields } = await autotask.Tickets.fieldInfo();
const status = fields.find((x) => x.name === "status");
status?.picklistValues?.forEach((v: PickListValue) => console.log(v.value, "→", v.label));

info(), fieldInfo(), and udfInfo() are typed to EntityInformationResult, FieldInformationResult, and UserDefinedFieldInformationResult.

Captured field metadata (generate your own — never bundled)

Picklist values, required/read-only flags, and reference targets are instance-specific, so they are not bundled. Generate a snapshot of your instance into your project:

# read-only against your instance; reads creds from ./.env in your project
npx autotask-enrich ./autotask-field-metadata.ts

.env keys recognized (case-insensitive): user (APIUSER/AUTOTASK_USER), secret (APISECRET/AUTOTASK_SECRET), tracking code (TRACKINGID/AUTOTASK_INTEGRATION_CODE), optional full zone URL (AUTOTASK_BASE_URL). The tool only issues GET requests — it can't create, modify, or delete anything.

The generated file exports lookup helpers and opt-in strict picklist types, which you import from your own path:

import {
  getFieldMeta, getPicklist, picklistLabel, picklistValue, FIELD_METADATA,
  type TicketPriorityValue,
} from "./autotask-field-metadata";

getFieldMeta("Tickets", "status")?.isRequired;                       // true
getFieldMeta("Tickets", "assignedResourceID")?.referenceEntityType; // "Resource"
getPicklist("Companies", "companyType");                            // [{ value: 1, label: "Customer" }, …]
picklistLabel("Tickets", "priority", 1);                            // "High"
picklistValue("Tickets", "priority", "Critical");                   // 4

const priority: TicketPriorityValue = 2; // 1 | 2 | 3 | 4 — `99` would be a compile error

These values mirror the instance they were captured from and change with your Autotask configuration — re-run the command to refresh. Add the generated file to .gitignore if your instance config is sensitive.


Runtime collection catalog

AUTOTASK_COLLECTIONS is a typed as const map of every REST collection:

import { AUTOTASK_COLLECTIONS, COLLECTION_NAMES } from "autotask-rest-api-types";

AUTOTASK_COLLECTIONS.Tickets;
// {
//   name: "Tickets", entity: "Ticket", model: "TicketModel", typed: true,
//   canQuery: true, canCount: true, canGet: true, canCreate: true,
//   canUpdate: true, canDelete: false, hasUserDefinedFields: true,
//   parentWriteOnly: false, deleteRequiresParent: false, parents: []
// }

const updatable = COLLECTION_NAMES.filter((n) => AUTOTASK_COLLECTIONS[n].canUpdate);

Design notes & limitations

  • Everything is | null and optional. Intentional — it forces you to handle Autotask's empty-as-null reality and partial responses. Narrow with a guard (if (c.companyName != null) …) when you need a concrete value.
  • Filter value is not correlated to the field's type. value is string | number | boolean. This is required so UDF names and dotted child-field paths (arbitrary strings) remain valid fields — TypeScript can't subtract known keys from string, so correlation would break those legitimate queries.
  • Picklist fields are number in the base interfaces (their valid codes are instance-specific). For real values + strict types, use your own generated ./autotask-field-metadata file (getPicklist, picklistLabel, and the *Value aliases — see Captured field metadata) or call fieldInfo() live.
  • Reference (foreign-key) fields are number in the base interfaces; your generated ./autotask-field-metadata (referenceEntityType) and live fieldInfo() both report the target entity.
  • CollectionNameEntityName. AUTOTASK_COLLECTIONS / CollectionName cover every REST path including action-only ones (Version, ZoneInformation, Authenticate, …). The client surfaces use EntityName / TypedCollectionName — only collections with a generated entity interface (typed: true).
  • exactOptionalPropertyTypes compatible. Types are authored as ?: T | null; the | null mirrors the wire. Works with the flag on or off.

Rate limits

Autotask allows 10,000 external requests per hour per database (across all integrations), and adds latency as you approach it (0.5s at 50%, 1s at 75%+). Check current usage via GET /ThresholdInformation (typed as ThresholdInformation). Combine with the collectAll / iterateAll helpers, which page at the 500-record server cap.


Regenerating from a newer spec

The committed swagger.json is the single source of truth. To refresh against a newer API version:

curl -s https://webservices.autotask.net/ATServicesRest/swagger/docs/v1 -o swagger.json
npm run generate   # rewrites src/generated/*
npm run build

scripts/generate.mjs is the only generator; hand-written code in src/core and src/client.ts is never touched.

To capture instance-specific picklist/field metadata for local development (read-only API calls, credentials from .env):

npm run enrich   # writes src/generated/field-metadata.ts (git-ignored, NOT published)
npm run build

The output is instance-specific, git-ignored, and excluded from the published tarball. Consumers of the published package generate their own with npx autotask-enrich (see Captured field metadata).


AI coding assistants

This package ships an agent skill at skills/autotask-rest-api/SKILL.md — the rules for generating correct Autotask code (the null-everywhere field shape, instance-specific picklists, PATCH vs PUT, the exact operator set) and a complete fetch client. Point your assistant at it, or have it read the runtime AUTOTASK_COLLECTIONS catalog for what each collection supports.

License

MIT.