> ## Documentation Index
> Fetch the complete documentation index at: https://tryagentai.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Create an escalation and resume your workflow from a signed callback.

# Quickstart

Use this flow when an agent reaches a decision it should not make alone. The agent sends the question, evidence, choices, and resume target. TryAgent routes the escalation by policy, records the reviewer decision or timeout path, and calls your workflow back with a signed event.

Want to run a complete workflow first? See the [examples](/examples).

## 1. Create a policy in TryAgent

In [TryAgent](https://tryagent.ai), create an escalation policy with the key `orders.auth_doc`. The policy owns reviewer routing, notification targets, SLA, and the default choice used when an escalation times out.

Keep those operations rules in policy. The agent should send only the decision content and the run handle it needs later.

## 2. Configure your runtime

Create an API key for your workspace. Agent-facing keys are bearer tokens that look like `ain_live_...`. Keys are issued with full escalation access by default, so the same key can create, read, and act on escalations — see [Manage escalations from the SDK](#manage-escalations-from-the-sdk).

```bash theme={null}
export TRYAGENT_API_KEY="ain_live_..."
export TRYAGENT_WEBHOOK_SECRET="whsec_..."
```

Install the SDK:

```bash theme={null}
pnpm add @tryagent/sdk
```

## 3. Send the escalation

```typescript theme={null}
import { TryAgent } from "@tryagent/sdk";

const tryagent = new TryAgent({
  apiKey: process.env.TRYAGENT_API_KEY!,
});

const escalation = await tryagent.escalate("orders.auth_doc", {
  agentId,
  runId,
  subject: {
    type: "order",
    id: "ord_4821",
    label: "Order #4821",
  },
  question: "The authorization document is missing a signature date. Continue?",
  evidence: [
    "Candidate name is present.",
    "Employer is present.",
    "Signature date is blank.",
  ],
  choices: [
    { id: "manual_review", label: "Send to manual review" },
    { id: "continue", label: "Continue anyway" },
  ],
  responseFields: [
    {
      type: "number",
      name: "approvedLimit",
      label: "Approved limit",
      min: 0,
      step: 0.01,
    },
    {
      type: "select",
      name: "riskLevel",
      label: "Risk level",
      options: [
        { value: "low", label: "Low" },
        { value: "high", label: "High" },
      ],
    },
  ],
  resume: {
    mode: "webhook",
    url: "https://api.example.com/tryagent/resume",
    secret: process.env.TRYAGENT_WEBHOOK_SECRET!,
  },
});

console.log(escalation.id, escalation.status);
```

The SDK sends `POST /escalations` with `Authorization: Bearer ain_live_...`. Required fields are `agentId`, `runId`, `subject`, `question`, `evidence`, and `choices`; the policy key is the first argument to `escalate`.

## 4. Verify the callback

TryAgent returns immediately with an open escalation. When a reviewer decides, or the SLA timeout resolves to the default choice, TryAgent sends a signed `POST` to `resume.url`.

The request body includes:

* `escalationId`
* `runId`
* `agentId`
* `policy`
* `choice`
* `response` when the escalation configured structured response fields
* `resolvedBy`
* `answeredBy`
* `answeredAt`

When `resume.secret` is set, TryAgent signs the exact JSON body with HMAC-SHA256 and sends:

* `x-tryagent-event`: `escalation.decided` for a reviewer decision, or `escalation.breached` when the SLA timeout resolves to the default choice. The `resolvedBy` body field (`human` or `timeout`) carries the same distinction.
* `x-tryagent-delivery: <delivery id>`
* `x-tryagent-signature: v1=<hex hmac>`

Verify the signature before applying the decision. The SDK ships
`webhooks.constructEvent`, which verifies the HMAC (constant-time, via Web Crypto
so it runs on Node, edge, and Workers) and returns the typed event — it throws
`WebhookSignatureError` when verification fails, so a returned event is safe to
act on.

## 5. Resume the run

Use `runId` as the durable handle for the paused workflow. For LangGraph, make it the `thread_id`; when the callback arrives, resume that thread with the reviewer decision. For the full LangGraph pattern, see [Use LangGraph interrupts](/guides/langgraph-interrupts).

```typescript theme={null}
import { TryAgent, WebhookSignatureError } from "@tryagent/sdk";

const tryagent = new TryAgent({ apiKey: process.env.TRYAGENT_API_KEY! });

export async function POST(request: Request) {
  // Pass the raw body — re-serializing JSON changes the bytes and breaks the signature.
  const body = await request.text();
  const signature = request.headers.get("x-tryagent-signature");

  let event;
  try {
    event = await tryagent.webhooks.constructEvent({
      payload: body,
      signature,
      secret: process.env.TRYAGENT_WEBHOOK_SECRET!,
    });
  } catch (err) {
    if (err instanceof WebhookSignatureError) {
      return Response.json({ error: "Invalid signature" }, { status: 401 });
    }
    throw err;
  }

  await resumeWorkflow(event.runId, {
    escalationId: event.escalationId,
    choice: event.choice,
    answeredBy: event.answeredBy,
    answeredAt: event.answeredAt,
  });

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

<Warning>
  Callbacks without `resume.secret` are sent unsigned. Always set a secret for
  production resume endpoints.
</Warning>

## Manage escalations from the SDK

Beyond `escalate`, the `escalations` resource covers the rest of the lifecycle:

```typescript theme={null}
await tryagent.escalations.list({ status: "open" });
await tryagent.escalations.get(escalationId);
await tryagent.escalations.acknowledge(escalationId);
await tryagent.escalations.decide(escalationId, { choice: "continue", reason: "Verified" });
await tryagent.escalations.cancel(escalationId, { reason: "Duplicate" });
```

Each method works with an `ain_live_` API key or a workspace user `token`/`getToken`.
Keys are issued with all escalation scopes by default, so every method is available
out of the box. Each route maps to a scope, so you can issue a narrower key via
`POST /api-keys`; a call needing a scope the key lacks returns `403`.

| Method                | Required scope            |
| --------------------- | ------------------------- |
| `escalate` / `create` | `escalations:write`       |
| `list`, `get`         | `escalations:read`        |
| `acknowledge`         | `escalations:acknowledge` |
| `decide`              | `escalations:decide`      |
| `cancel`              | `escalations:cancel`      |
