API ReferenceIncident workflow
All endpoints

Create and move an incident through its workflow

Discover workspace configuration before creating an incident, then carry returned IDs and revisions through later calls.

Lifecycle recipe

  1. List workflow statuses and select the configured default key.
  2. List custom-field definitions. Supply a value with the correct type for every required_on_creation field.
  3. Create the incident with a fresh Idempotency-Key and a short_title. Set create_slack_channel to false and invite_usergroups to an empty array when the workflow must not contact Slack.
  4. Use incident.id for numeric-ID routes and incident.revision for the first protected PATCH.
  5. Use resolve, close, and reopen operations for lifecycle transitions. Inspect each returned incident before the next change.

Run the workflow example

Set TELLAGEN_COMPANY to your workspace slug and TELLAGEN_TOKEN to your API key. For a local server, set TELLAGEN_API_ORIGIN to its address; you can then omit TELLAGEN_COMPANY.

The key needs incidents:read, incidents:write, and custom_fields:read. Its owner needs an active responder seat.

Choose Node.js 22 or later, or Go 1.24 or later. Both examples use the standard library and need no extra packages.

Each example creates and resolves an incident. Run it in a workspace where you intend these changes.

The examples stop before creation when custom fields are required. They list the field IDs and types.

For a workspace with required fields, adapt the required-field guard and supply the values in custom_field_values before running the example.

After the timeline event, the example reads the incident again. This read supplies the current revision for resolution.

Workflow example

Example

Save this code as workflow.mjs. Run it with node workflow.mjs.

import crypto from "node:crypto";

async function main() {
  const token = process.env.TELLAGEN_TOKEN;
  if (!token) throw new Error("Set TELLAGEN_TOKEN.");
  let origin = process.env.TELLAGEN_API_ORIGIN;
  if (!origin) {
    const company = process.env.TELLAGEN_COMPANY;
    if (!company) throw new Error("Set TELLAGEN_COMPANY or TELLAGEN_API_ORIGIN.");
    origin = "https://" + company + ".api.tellagen.com";
  }
  origin = origin.replace(/\/+$/, "");

  async function request(method, path, body, extraHeaders = {}) {
    const response = await fetch(origin + path, {
      method,
      headers: {
        Authorization: "Bearer " + token,
        "Content-Type": "application/json",
        ...extraHeaders,
      },
      body: body === undefined ? undefined : JSON.stringify(body),
      signal: AbortSignal.timeout(30_000),
    });
    const text = await response.text();
    if (!response.ok) throw new Error("HTTP " + response.status + ": " + text);
    return JSON.parse(text);
  }

  const statuses = await request("GET", "/api/v1/configuration/workflow-statuses");
  const defaultStatus = statuses.workflow_statuses.find((status) => status.is_default);
  if (!defaultStatus) throw new Error("No default workflow status was returned.");

  async function listCustomFields() {
    const definitions = [];
    let cursor;
    for (let pageNumber = 0; pageNumber < 1_000; pageNumber++) {
      const params = new URLSearchParams({ limit: "100" });
      if (cursor) params.set("cursor", cursor);
      const page = await request("GET", "/api/v1/custom-fields?" + params);
      definitions.push(...page.fields);
      if (!page.pagination.has_more) return definitions;
      cursor = page.pagination.next_cursor;
      if (!cursor) throw new Error("has_more was true without next_cursor");
    }
    throw new Error("Stopped after 1000 custom-field pages.");
  }
  const definitions = await listCustomFields();
  const required = definitions.filter((field) => field.required_on_creation);
  if (required.length) {
    const summary = required.map((field) => field.id + " (" + field.field_type + ")").join(", ");
    throw new Error("Set custom_field_values for required field IDs before creating: " + summary);
  }

  // Keep each create key with its original body if you later reconcile a failed request.
  const createKey = "lifecycle-" + crypto.randomUUID();
  const created = await request("POST", "/api/v1/incidents", {
    title: "API lifecycle verification incident",
    short_title: "api-check",
    status: defaultStatus.key,
    create_slack_channel: false,
    invite_usergroups: [],
    custom_field_values: [],
  }, { "Idempotency-Key": createKey });
  const incident = created.incident;
  const path = "/api/v1/incidents/" + incident.id;
  await request("PATCH", path, {
    impact_summary: "Lifecycle example created this incident.",
  }, { "If-Tellagen-Resource-Version": String(incident.revision) });

  const timelineKey = "timeline-" + crypto.randomUUID();
  const timeline = await request("POST", path + "/timeline", {
    body: "Lifecycle example recorded this verification step.",
    type: "change",
  }, { "Idempotency-Key": timelineKey });

  // Timeline creation changes the incident revision. Read it before resolving.
  const current = await request("GET", path);
  await request("POST", path + "/resolve", {
    resolution_note: "Lifecycle example completed.",
    expected_revision: current.incident.revision,
  });
  const final = await request("GET", path);
  console.log(JSON.stringify({
    id: incident.id,
    revision: final.incident.revision,
    timeline_event_id: timeline.event.id,
  }));
}

main().catch((error) => {
  const token = process.env.TELLAGEN_TOKEN;
  const message = error instanceof Error ? error.message : String(error);
  console.error(token ? message.replaceAll(token, "[redacted]") : message);
  process.exitCode = 1;
});

Workflow, archive, and references

A workflow status describes operational state. Resolve changes an incident to resolved and its issue type to postmortem; close ends it without marking it resolved; reopen returns a resolved or closed incident to active. Archive controls normal collection visibility and editability. It is a separate action from workflow status.

Numeric ID routes use incident.id. GET /api/v1/incident-references/{reference} accepts a permanent reference from Tellagen links and messages, not a numeric ID. That opaque reference continues to identify the incident after editable fields change.

When incident creation includes doc_url and import_source_timeline_event, inspect source_timeline_import. Its status can be imported, failed, unavailable, invalid, or unsupported; HTTP 201 alone does not prove that evidence import succeeded.