Skip to content
Changelog

Report a bug

Deployment API Reference

An automation entrypoint file exports two things:

  1. A named export, deployment, that tells Type when to run your code.
  2. A default export, a class, with one method for each subscription.
src/main.ts
import type {
AutomationContext,
AutomationDeployment,
RecurringEvent,
} from "@type-dot-com/sdk/automations";
export const deployment = {
subscriptions: {
hourly: {
title: "Hourly check",
description: "Record the scheduled time in run history",
source: { kind: "recurring", schedule: { kind: "interval", everySeconds: 3600 } },
},
},
} satisfies AutomationDeployment;
export default class HourlyCheck {
hourly(ctx: AutomationContext, event: RecurringEvent) {
return ctx.setOutput({ checkedAt: event.scheduledAt });
}
}

Type reads deployment in an isolated VM when you deploy. Type validates the result again on the server. No code runs on your computer.

interface AutomationDeployment {
expiration?: AutomationExpiration;
subscriptions: Record<string, DeploymentSubscription>;
webhooks?: Record<string, { connectionId: string; entrypoint: string }>;
mode?: "code" | "code_with_optional_agent";
agent?: { instructions: string };
failurePolicy?: "pause" | "pause_and_investigate";
}
FieldRequiredDefaultMeaning
expirationNoNo limitWhen the automation stops. See Expiration.
subscriptionsYes—The events and schedules that run the automation. At least one.
webhooksNo{}Custom webhook adapters to install with this automation. See Webhook Adapter API.
modeNo"code""code": code only. "code_with_optional_agent": code can give work to the managed agent.
agentOnly with code_with_optional_agent—Standing instructions for the AI. instructions must not be empty.
failurePolicyNo"pause_and_investigate"Accepted for compatibility. Both values have the same effect today: a failed run pauses the automation and starts a repair.

The object must not have other fields. Type rejects unknown fields.

An automation can stop on its own. Set one optional expiration in the deployment export, or in type.json. When the limit comes, Type stops starting live runs for all subscriptions of the automation.

// Five hours after the automation first activates:
expiration: { kind: "duration", afterSeconds: 5 * 60 * 60 }
// After ten live runs:
expiration: { kind: "runs", maxRuns: 10 }
// At a fixed time. The timestamp must have `Z` or a UTC offset:
expiration: { kind: "timestamp", at: "2026-10-10T10:00:00-06:00" }
KindFieldRules
durationafterSeconds1 to 315,576,000 (10 years). Counts from the first activation, also while paused.
runsmaxRuns1 to 2,147,483,647.
timestampatAn ISO date-time with Z or an offset. A time in the past expires the automation at once.

How expiration works:

  • Use one kind of limit for each automation. If you omit expiration, the automation has no limit.
  • Each new live run counts toward maxRuns, also failed runs, skipped runs, repair runs, and follow-up runs. A replay of the same run ID does not count again.
  • Fixture runs do not count.
  • A run that started before the limit can finish and save its result.
  • Pause, resume, and deploy keep the first activation time and the run count.
  • If you change the limit, the new limit applies to the same lifetime. If you remove the limit, runs start again.
  • expiration in type.json overrides the source. null in type.json removes it.
  • A deploy flag overrides both, for that deploy only. See CLI Reference.

Each key in subscriptions is a subscription ID. It is also the name of the class method that Type calls.

interface DeploymentSubscription {
title: string; // 1 to 200 characters
description: string; // 0 to 2,000 characters
source: IntegrationEventSource | RecurringSource;
schema?: ZodType; // integration sources only
match?: AutomationMatcher; // integration sources only
debounce?: AutomationDebounce; // integration sources only
}
  • Start with a lowercase letter.
  • Use only letters, digits, and _. The maximum length is 64 characters.
  • Do not use these names: constructor, prototype, then, toString, valueOf, hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toLocaleString.
  • Keep a key stable. The checkpoint belongs to the key. A new key starts with no checkpoint.

A source tells Type what starts a run. There are two kinds.

Runs the method on a schedule.

interface RecurringSource {
kind: "recurring";
schedule:
| { kind: "interval"; everySeconds: number } // positive integer
| { kind: "cron"; expression: string; timezone: string };
connectionId?: string; // optional related connection, "orgint_..."
}
FieldRules
schedule.everySecondsAt least 60, and a multiple of 60. For example, 900 is every 15 minutes.
schedule.expressionA cron expression with a future run. It can run at most one time each minute.
schedule.timezoneAn IANA time zone, for example America/New_York. Must not be empty.
connectionIdOptional. A related connection, for example the service that the job reads. Type stores it but does not use it for access. It is not a credential.

The method gets a RecurringEvent. You cannot use match, schema, or debounce with a recurring source. See Run on a Schedule.

Runs the method when a connected app sends an event.

interface IntegrationEventSource {
kind: "integration";
connectionId: string; // "orgint_..."
webhook?: string; // a custom webhook key; omit for Slack and GitHub
events: string[]; // 1 to 100 unique event names
resources?: Record<string, string[]>; // optional resource filters
}
FieldRules
connectionIdThe ID of a connection in the Space. It matches orgint_ followed by lowercase letters and digits.
webhookThe key of a custom webhook adapter on this connection. Omit it for Slack and GitHub, which Type receives for you.
events1 to 100 unique names. Each name is 1 to 200 characters. See Events reference.
resourcesOptional. Each key is a resource type. Each value is a list of resource IDs. See Resource filters.

Type receives events for these connections:

ConnectionHow
SlackBuilt in. Omit webhook.
GitHubBuilt in. Omit webhook.
Any connection with a custom webhook adapterSet webhook to the adapter key.

Other connections, for example Linear and Gmail, cannot send events to a code automation yet. The deploy fails at the subscription stage. For these services, use a recurring source and read the service on a schedule.

Example: run only for messages in one Slack channel.

source: {
kind: "integration",
connectionId: "orgint_abc123",
events: ["message"],
resources: { channel: ["C0123456789"] },
},

The schema field is optional. It applies only to integration sources.

If you omit schema, Type uses a default contract:

  • For a Slack message subscription with a slack.message matcher, the method gets a SlackMessageEvent.
  • For all other events, the method gets an IntegrationEvent. Type does not check the fields in data.

If you give a Zod schema, Type converts it to JSON Schema and saves it. Type checks each event against the saved JSON Schema before your method runs. Follow these rules:

  • The schema describes the full event: type, id, resourceId, resourceType, and data.
  • Each event in source.events must have exactly one schema variant with a required literal type. Use z.discriminatedUnion("type", [...]) for more than one event.
  • Do not use .refine(), .superRefine(), .transform(), .trim(), or .toLowerCase(). JSON Schema cannot save these rules, so Type rejects the schema. Do these checks in the method.
  • Do not use allOf, not, or if constructs. References are allowed only to #/$defs/....
  • Use z.looseObject() for provider data, so new provider fields do not fail the run.
  • Use .nullish() for fields that can be missing or null. .optional() does not accept null.

Use the schema’s output type as the method’s event type:

const orderEvent = z.looseObject({
type: z.literal("order.created"),
id: z.string(),
data: z.looseObject({ orderId: z.string(), total: z.number() }),
});
export default class {
onOrder(ctx: AutomationContext, event: z.output<typeof orderEvent>) {
return ctx.setOutput({ orderId: event.data.orderId });
}
}

If an event does not match the schema, the method does not run. Type keeps the event for diagnosis and pauses the subscription.

The match field filters events on the server, before Type starts your code. Your method does not run for a filtered event. See the Matchers reference.

match: { "slack.message": { contains: "deploy failed" } },

Omit match, or use { handler: "custom" }, to send every event to your method.

Debounce groups events, then runs the method one time for the group. It applies only to integration sources.

interface AutomationDebounce {
key: string[]; // 1 to 8 unique field paths, for example ["resourceId"]
quietPeriodMs: number; // 1 to 86,400,000
maxWaitMs: number; // 1 to 86,400,000, and at least quietPeriodMs
delivery: "batch" | "latest";
}
FieldMeaning
keyField paths in the event, joined with ., for example data.repositoryFullName. Events with the same values go in the same group. Each path must be a required string, number, integer, or boolean field in every event variant. A path can have at most 16 parts.
quietPeriodMsType waits this long after the last new event in a group. Each new event starts the wait again.
maxWaitMsThe maximum wait from the first event in the group.
delivery"batch": the method gets an array of 1 to 100 events. "latest": the method gets only the last event.

How groups work:

  • Type always adds the workspace, automation, subscription, connection, and code revision to the group identity.
  • A duplicate event does not extend the wait.
  • A group closes early at 100 events or 1 MiB of event data. Extra events go to a new group.
  • An event larger than 1 MiB is rejected.
  • latest orders events by the time Type received them, not by provider timestamps.
  • The deadline is the earliest time that the run can start. It is not a promise of an exact start time.

With delivery: "batch", the method event type is an array:

onPush(ctx: AutomationContext, events: Array<z.output<typeof pushEvent>>) {
return ctx.setOutput({ count: events.length });
}

The default export is a class. Type creates one instance for each run.

  • For each subscription key, the class must have a method with the same name. If a method is missing, the deploy fails with “Subscription <key> requires a matching method”.
  • Each method takes (ctx, event). See the Context reference.
  • A method can be async.
  • Return err(error) to fail the run. See method return values.
  • Do not keep state in class fields or module variables. Each run starts in a new VM. Use checkpoints.

Helper functions and other methods are allowed. Type calls only the methods that match subscription keys.

When you deploy, Type checks the project in stages. The CLI shows each stage as it starts, succeeds, or fails. The first failure stops the deploy.

StageWhat Type checks
validationThe files and type.json. The entrypoint typechecks with the SDK that Type runs. The deployment export is valid. Each event schema can be saved as JSON Schema. The class has a method for each subscription.
fixturesThe fixtures of each webhook adapter, then each case in fixtures.json.
subscriptionEach subscription can use its connection and its events. Type registers the subscription.
activationType saves the new revision and enables the automation.
deploymentThe deploy is complete.

If a stage fails, the active revision does not change. See Create and Manage Automations.