Terse workflows combine two sources:Documentation Index
Fetch the complete documentation index at: https://docs.useterse.ai/llms.txt
Use this file to discover all available pages before exploring further.
- the public
terse-sdkpackage - the generated helpers in
src/terse.generated.ts
terse generate, integration trigger builders and skill factories are grouped under top-level Triggers and Skills objects (for example Triggers.github.onPROpened(...), Triggers.heyReach.onMessageReplyReceived(...), Skills.slack({ channel: ... })). System-style helpers live there too: Triggers.schedule.cron(...), Triggers.webhook.onRequest(), and Triggers.webMonitor.onEvent(...). Prefer these namespaces instead of older per-integration root exports such as GitHub.*, Slack.*, or Schedule.* / Webhook.*.
Hosted workflow runs use the Node.js runtime (
terse-sdk and your generated src/terse.generated.ts). Terse does not ship a Python runtime or Python workflow SDK.Core SDK imports
Import these fromterse-sdk:
createJob: registers a workflow (call at module top level or from imported modules)CreateJobParameters: the object shape passed tocreateJobTerse: client forhandleTriggerwhen you self-host the webhook endpointTERSE_JOB_WEBHOOK_TRIGGER_PATH: mount this path on your HTTP server when wiringhandleTrigger(matches what the Terse backend calls)TerseAgent: create withTerseAgent.create()insideonTriggerEventType: enum for values on streamedTerseAgentresultsTrigger: the base event interface- Specific event types:
GithubTrigger,GithubPRTrigger,CronTrigger,ManualSampleTrigger,WebhookTrigger(optionally generic over the JSON body type), HeyReach types such asHeyReachMessageReplyReceivedTriggerandHeyReachTrigger, and other per-integration exports (seeterse-sdkpackage entry) - Config helpers re-exported for trigger construction:
HeyReachInputConfig, and theHeyReachEventTypeobject for event-type constants - Run history types (re-exported for Activity and API payloads):
RunHistoryRecord,RunHistoryStatus,RunHistoryTrigger,RunHistoryDecision,RunHistoryAction formatTriggerForAgentanddebugTrigger: helpers re-exported fromterse-typesfor plain trigger payloads (without the SDK methods below)getJobContext,runWithJobContext, typeTerseJobContext: async job context (session id, run id, API base URL). The SDK reads this when each request is made so agent runs and deterministic tool calls stay attributed to the right session and run. OutsiderunWithJobContext, setTERSE_BACKEND_URLif needed (defaulthttps://app.useterse.ai/api) and optionallyTERSE_RUN_IDfor run attribution.
zod:
Authentication and TERSE_API_KEY
Terse, TerseAgent, and other SDK calls that reach Terse send Authorization: Bearer … from process.env.TERSE_API_KEY. Terse API tokens use the terse_ prefix.
-
User tokens are the API tokens you create in the Terse app or get from
terse login. Use them for local runs, the CLI, and self-hostedhandleTriggerservers. They authenticate to the full set of SDK routes your workflow needs (including deploy, codegen, and runtime calls). - Project-scoped tokens are minted automatically inside hosted Modal sandboxes. They are limited to SDK runtime endpoints for that project (for example agent runs, tool execution, approvals, and session streams) and cannot stand in for a user token on organization or integration-management routes. Terse removes the sandbox token when the run completes.
- Tokens with an expiration stop working when they expire; the API responds with 401 and an expired-token message.
Trigger payloads
Exported trigger types are the canonical trigger object plus two methods used everywhere Terse turns an event into prompts or logging:formatForAgentRunner()returns a string to include in agent prompts (matches how the platform formats the event for the model)debugLog()returns a one-line description for logs and CLI sample lists
onTrigger and filter callbacks receive these enriched objects, so you can call event.formatForAgentRunner() directly. For plain Trigger values (for example in tests), use formatTriggerForAgent(event) and debugTrigger(event) from terse-sdk.
createJob(...)
Registers a workflow at load time. The CLI imports your entry file (src/terse.jobs.ts by default), so every createJob() call that runs during that import is included—split definitions across files with side-effect imports (for example import "./jobs/myWorkflow") if you prefer.
Duplicate name values throw an error when the second job registers. At most one webhook trigger is allowed per workflow.
| Field | Type | Description |
|---|---|---|
name | string | Unique workflow identifier. Used to match workflows across deploys and in terse test. |
triggers | typed trigger array | One or more triggers that start the workflow. |
filter | (event) => boolean | Promise<boolean> | Optional function to skip the run for specific events. See filtering events. |
onTrigger | (event) => Promise<void> | The workflow handler. Called once per trigger event. |
remoteServerUrl | string | Optional override for the Terse API base URL (otherwise TERSE_BACKEND_URL or https://app.useterse.ai/api). |
skills, toolApprovals, and the agent prompt to TerseAgent.create() inside onTrigger, not on createJob.
Terse
Use a Terse instance for handleTrigger, which verifies signed webhook payloads from the Terse backend and runs the matching registered workflow. You do not need new Terse() only to call createJob().
TerseAgent
Create the agent inside onTrigger with TerseAgent.create(). Job context (session, run, API base URL) is picked up automatically from async context when the handler runs on the platform or via the CLI. The same context applies to TerseAgent.executeTool and to generated toolbox calls.
run(userMessage)
Streams the model run. Returns an async iterable of result objects (TextResult, FinalOutputResult, tool events, and so on).
run when you want to stream output progressively.
runAndWait(userMessage)
Waits for the model to complete and returns the final output string.
runAndWait(userMessage, outputSchema)
Pass a zod schema to request structured output. The SDK sends the schema with the run, parses the final JSON, and validates it before returning.
TerseAgent.executeTool(toolName, params?)
Static method. Calls a named tool directly, bypassing the LLM. Generated toolbox and agent.tools.* helpers use this path, so string-based and typed deterministic calls behave the same.
slackUserId (Slack member U…) to send a 1:1 DM; Terse opens the conversation if one does not exist yet. If you pass channelId and slackUserId, the message is sent to channelId. With codegen, prefer SlackUser.*.userId from src/terse.generated.ts for member ids.
Use TerseAgent.executeTool when you want guaranteed execution of a specific tool by name (for example when the name is only known at runtime).
toolbox.* (generated)
terse generate writes a toolbox export in src/terse.generated.ts with the same typed namespaces as agent.tools.*. Import toolbox to call integration tools deterministically without constructing a TerseAgent and without listing integrations in skills.
agent.tools.*
Generated helpers attach deterministic wrappers under agent.tools.*. These call integration actions directly, not through the LLM.
agent.tools.* for guaranteed side effects when you already pass skills to TerseAgent.create() (or when the integration is implied by a trigger). For the same calls without listing skills, use generated toolbox instead.
Structured output
UserunAndWait(message, schema) when you need typed, validated JSON. Use runAndWait(message) when you need free-form text.
Filtering events
Usefilter to skip runs for events that don’t match your criteria. Return true to run, false to skip.
Tool approvals
List tool names intoolApprovals on TerseAgent.create() to require human approval before those tools execute. During local testing, the CLI prompts in the terminal. In production, approval requests surface in the Terse app under Notifications.
toolApprovals for workflows that write to production systems during early development, or when compliance requires a human in the loop.
Events
Event typing depends on the trigger.| Trigger source | Event type |
|---|---|
| Attio record events | Trigger |
| Slack actions | Typed Slack action payloads |
| Scheduled cron triggers | CronTrigger |
| Scheduled interval triggers | CronTrigger |
| Manual trigger | ManualSampleTrigger |
| Webhook HTTP requests | WebhookTrigger |
| HeyReach outreach events | HeyReachTrigger (union) or the specific HeyReach*Trigger for your handler |
Trigger is the common base interface. Use the most specific trigger event type your trigger exposes. For webhooks, use Triggers.webhook.onRequest<YourBodyType>() from terse.generated so event.body matches the JSON you POST to the webhook URL.
Generated helpers
src/terse.generated.ts is created by terse generate. Do not edit it by hand.
It exports:
Triggers: per-integration trigger builders (when connected), includingTriggers.heyReachwhen HeyReach is connected, plusTriggers.schedule,Triggers.webhook, andTriggers.webMonitorSkills: per-integration skill factories (when connected) plus built-inSkills.web()andSkills.imageEdit()- Workspace resource constants (for example
Repos,SlackChannel,SlackUser,AttioObject.*statics) and other typed prelude exports your workspace needs toolbox: deterministic tool wrappers without an agent orskillsfilter- Deterministic tool wrappers on
TerseAgentviaagent.tools.*(filtered byskills)
Bring your own API
Call REST endpoints fromonTrigger with fetch (or your HTTP client of choice), then pass results into TerseAgent.runAndWait or deterministic agent.tools.* / TerseAgent.executeTool calls. Pair that with Triggers.webhook.onRequest when an external system should start the workflow, and use Skills.web() when you want the built-in web research tools on the agent.