Deployment API Reference
An automation entrypoint file exports two things:
- A named export,
deployment, that tells Type when to run your code. - A default export, a class, with one method for each subscription.
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.
AutomationDeployment
Section titled “AutomationDeployment”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";}| Field | Required | Default | Meaning |
|---|---|---|---|
expiration | No | No limit | When the automation stops. See Expiration. |
subscriptions | Yes | — | The events and schedules that run the automation. At least one. |
webhooks | No | {} | Custom webhook adapters to install with this automation. See Webhook Adapter API. |
mode | No | "code" | "code": code only. "code_with_optional_agent": code can give work to the managed agent. |
agent | Only with code_with_optional_agent | — | Standing instructions for the AI. instructions must not be empty. |
failurePolicy | No | "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.
Expiration
Section titled “Expiration”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" }| Kind | Field | Rules |
|---|---|---|
duration | afterSeconds | 1 to 315,576,000 (10 years). Counts from the first activation, also while paused. |
runs | maxRuns | 1 to 2,147,483,647. |
timestamp | at | An 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.
expirationintype.jsonoverrides the source.nullintype.jsonremoves it.- A deploy flag overrides both, for that deploy only. See CLI Reference.
Subscriptions
Section titled “Subscriptions”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}Subscription key rules
Section titled “Subscription key rules”- 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.
Sources
Section titled “Sources”A source tells Type what starts a run. There are two kinds.
Recurring source
Section titled “Recurring source”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_..."}| Field | Rules |
|---|---|
schedule.everySeconds | At least 60, and a multiple of 60. For example, 900 is every 15 minutes. |
schedule.expression | A cron expression with a future run. It can run at most one time each minute. |
schedule.timezone | An IANA time zone, for example America/New_York. Must not be empty. |
connectionId | Optional. 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.
Integration source
Section titled “Integration source”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}| Field | Rules |
|---|---|
connectionId | The ID of a connection in the Space. It matches orgint_ followed by lowercase letters and digits. |
webhook | The key of a custom webhook adapter on this connection. Omit it for Slack and GitHub, which Type receives for you. |
events | 1 to 100 unique names. Each name is 1 to 200 characters. See Events reference. |
resources | Optional. Each key is a resource type. Each value is a list of resource IDs. See Resource filters. |
Type receives events for these connections:
| Connection | How |
|---|---|
| Slack | Built in. Omit webhook. |
| GitHub | Built in. Omit webhook. |
| Any connection with a custom webhook adapter | Set 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"] },},Event schema
Section titled “Event schema”The schema field is optional. It applies only to integration sources.
If you omit schema, Type uses a default contract:
- For a Slack
messagesubscription with aslack.messagematcher, the method gets aSlackMessageEvent. - For all other events, the method gets an
IntegrationEvent. Type does not check the fields indata.
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, anddata. - Each event in
source.eventsmust have exactly one schema variant with a required literaltype. Usez.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, orifconstructs. 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 ornull..optional()does not acceptnull.
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.
Matchers
Section titled “Matchers”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
Section titled “Debounce”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";}| Field | Meaning |
|---|---|
key | Field 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. |
quietPeriodMs | Type waits this long after the last new event in a group. Each new event starts the wait again. |
maxWaitMs | The 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.
latestorders 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 automation class
Section titled “The automation class”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.
Validation order
Section titled “Validation order”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.
| Stage | What Type checks |
|---|---|
validation | The 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. |
fixtures | The fixtures of each webhook adapter, then each case in fixtures.json. |
subscription | Each subscription can use its connection and its events. Type registers the subscription. |
activation | Type saves the new revision and enables the automation. |
deployment | The deploy is complete. |
If a stage fails, the active revision does not change. See Create and Manage Automations.