You should add a runTask() function to your integration, before adding individual Tasks. It will be used by all Tasks, and allows users to use any function in the official SDK as a Task.

export class Github implements TriggerIntegration {

  runTask<T, TResult extends Json<T> | void>(
    key: IntegrationTaskKey,
    //change Octokit to the official SDK for your integration
    callback: (client: Octokit, task: IOTask, io: IO) => Promise<TResult>,
    options?: RunTaskOptions,
    errorCallback?: RunTaskErrorCallback
  ): Promise<TResult> {
    if (!this._io) throw new Error("No IO");
    if (!this._connectionKey) throw new Error("No connection key");

    return this._io.runTask(
      (task, io) => {
        if (!this._client) throw new Error("No client");
        return callback(this._client, task, io);
        //change this to the slug for your integration (lowercase, no spaces)
        icon: "github",
        retry: retry.standardBackoff,
        ...(options ?? {}),
        connectionKey: this._connectionKey,


Matching the original SDK structure

The structure of the integrtion should closely map to the official Node SDK for that API.

APIOfficial SDKTrigger.Dev Task
GitHub (octokit)

Closely resembling the official SDK makes it far easier for developers to use integrations. Aim for 1:1 mapping wherever possible.

Adding Tasks

Any tasks you add will use the runTask function you added above.

Remember we want to structure our integration to match the original SDK. If an SDK has a client.forms.get function, we want to add a myintegration.forms.get task.

This requires adding a bit of structure to the SDK, to add the Forms object, the get function and use runTask inside them.

Create the structure

In this example, we’ll add io.github.issues.create. This will allow users to create a GitHub issue in their jobs.

Comments inline are for instructional purposes only, and should not be included in your final code.

import { IntegrationTaskKey } from "";
import { GitHubReturnType, GitHubRunTask, onError } from "./index";
import { Octokit } from "octokit";

export class Issues {
  //the runTask you created in the other file, we'll use this for all Tasks
  runTask: GitHubRunTask;

  constructor(runTask: GitHubRunTask) {
    this.runTask = runTask;

    //this must be the first parameter, it's used to identify the task
    key: IntegrationTaskKey,
    //if possible use the official SDK type. Here we had to define our own.
    params: { title: string; owner: string; repo: string }
    //you must define the return type (it will be a promise, in this case grabbed from the official SDK)
  ): GitHubReturnType<Octokit["rest"]["issues"]["create"]> {
    //use runTask
    return this.runTask(
      async (client, task) => {
        //the official SDK is used here
        const result = await{
          owner: params.owner,
          repo: params.repo,
          title: params.title,
      //all of these properties are displayed in the dashboard
        name: "Create Issue",
        //properties provide a great experience for developers debugging a Run
        properties: [
            label: "Owner",
            text: params.owner,
            label: "Repo",
            text: params.repo,
            label: "Title",
            text: params.title,
      //you can define a custom error function, or omit this.

Notice how the params are the second argument, that’s because the first argument is always the task key. See our Keys and Resumability docs for more on why this is important.

//This type is used in the Issues class above
export type GitHubRunTask = InstanceType<typeof Github>["runTask"];

export class Github implements TriggerIntegration {

  //this allows us to do `io.github.issues.create()`
  get issues() {
    //the bind is needed to preserve the `this` context
    return new Issues(this.runTask.bind(this));



onError function

You can optionally have logic in the onError param. See the reference for runTask for more info.

The GitHub integration uses this to retry rate-limited requests when the rate limit resets:


function isRequestError(error: unknown): error is RequestError {
  return typeof error === "object" && error !== null && "status" in error;

export function onError(error: unknown) {
  if (!isRequestError(error)) {

  // Check if this is a rate limit error
  if (error.status === 403 && error.response) {
    const rateLimitRemaining = error.response.headers["x-ratelimit-remaining"];
    const rateLimitReset = error.response.headers["x-ratelimit-reset"];

    if (rateLimitRemaining === "0" && rateLimitReset) {
      const resetDate = new Date(Number(rateLimitReset) * 1000);

      return {
        retryAt: resetDate,