Paperclip Agent API Contract: Heartbeat Reporting and Task Operations Explained

Paperclip's agent API contract exposes REST endpoints under /api for heartbeat status reporting, runtime state management, and task session operations, with all payloads defined in TypeScript interfaces and documented in the OpenAPI spec.

The Paperclip control-plane uses a strict contract to coordinate distributed agents. This article breaks down the exact API endpoints, request/response shapes, and source locations that define how agents report their heartbeat status and manage task sessions during execution.

Core Runtime State and Heartbeat Endpoints

All agent-facing endpoints reside in server/src/routes/agents.ts. The heartbeat system revolves around the runtime state — a singleton record per agent that tracks liveness, scheduling eligibility, and task session bindings.

GET /agents/:id/runtime-state

Retrieve the authoritative heartbeat metadata for an agent. This endpoint returns the RuntimeState interface defined in server/src/services/heartbeat.ts:

export interface RuntimeState {
  agentId: string;
  companyId: string;
  status: "idle" | "paused" | "running" | "error";
  intervalSec: number;            // heartbeat interval (seconds)
  enabled: boolean;               // true if heartbeat is configured
  lastHeartbeatAt: string | null; // ISO-8601 timestamp
  schedulerActive: boolean;       // true when both enabled and status-eligible
}

The schedulerActive boolean is computed — it is true only when enabled === true and status permits scheduling (typically "idle" or "running"). The intervalSec field controls how frequently the agent should emit heartbeats.

GET /agents/:id/task-sessions

List all task sessions currently bound to the agent. Each session represents a long-running unit of work that survives across individual heartbeat pings. The response is an array of TaskSession objects:

export interface TaskSession {
  sessionId: string;
  taskKey?: string | null;        // optional stable identifier for the task
  startedAt: string;
  lastSeenAt: string;
  status: "active" | "completed" | "failed";
  sessionParamsJson?: string | null; // redacted payload
}

The lastSeenAt timestamp updates when the agent reports progress on this specific session. The taskKey field allows idempotent session creation — agents can reference stable task names rather than server-assigned sessionIds.

Task Session Lifecycle Management

POST /agents/:id/runtime-state/reset-session

Reset or create a task session for the agent. This endpoint is critical for recovery scenarios — it clears stale sessions and optionally initializes a new one with a specific taskKey.

Parameter Type Required Description
taskKey string No Stable identifier for the task; omit to reset without binding

The response returns the updated RuntimeState, reflecting any session changes immediately.

Example: Resetting a session with Node.js fetch:

async function resetSession(agentId, token, taskKey = null) {
  const body = taskKey ? { taskKey } : {};
  const res = await fetch(
    `https://your-paperclip-instance/api/agents/${agentId}/runtime-state/reset-session`,
    {
      method: "POST",
      headers: { 
        "Content-Type": "application/json", 
        Authorization: `Bearer ${token}` 
      },
      body: JSON.stringify(body),
    }
  );
  return await res.json(); // → updated RuntimeState
}

Administrative Heartbeat Monitoring

GET /instance/scheduler-heartbeats

Returns a cross-tenant view of all agents with scheduler-driven heartbeats enabled. This endpoint is restricted to admin callers and powers the system health dashboard.

The response shape InstanceSchedulerHeartbeatAgent (also in server/src/routes/agents.ts) includes denormalized company and agent metadata:

export interface InstanceSchedulerHeartbeatAgent {
  id: string;
  companyId: string;
  companyName: string;
  companyIssuePrefix: string;
  agentName: string;
  agentUrlKey: string;
  role: string;
  title: string;
  status: string;
  adapterType: string;
  intervalSec: number;
  heartbeatEnabled: boolean;
  schedulerActive: boolean;
  lastHeartbeatAt: string | null;
}

Example: Fetching global heartbeat status:

async function listSchedulerHeartbeats(adminToken) {
  const res = await fetch(
    `https://your-paperclip-instance/api/instance/scheduler-heartbeats`,
    { headers: { Authorization: `Bearer ${adminToken}` } }
  );
  return await res.json(); // → InstanceSchedulerHeartbeatAgent[]
}

Agent Control Operations

Three state mutation endpoints manage agent availability without modifying heartbeat configuration directly:

Endpoint Effect Resulting Status
POST /agents/:id/pause Stops heartbeats and terminates running tasks "paused"
POST /agents/:id/resume Re-enables heartbeat processing "idle"
POST /agents/:id/clear-error Acknowledges and clears terminal error state Previous non-error status

All three return the updated Agent object. The pause/resume pair is preferred over directly mutating enabled — it preserves the configuration while temporarily disabling execution.

Authentication and Authorization Rules

Every endpoint enforces actor-based access control as implemented in the route handlers:

  • Agent actor: The bearer token must identify as agent type with matching agentId
  • Board user: Requires assertBoard and assertCanReadConfigurations (or equivalent) permissions for the agent's company

The OpenAPI specification in server/src/routes/openapi.ts documents these constraints, though runtime enforcement happens in the Express route handlers.

Client-Side API Wrapper

The Paperclip UI consumes these same endpoints through ui/src/api/heartbeats.ts. This module mirrors the server contract, ensuring type consistency across the stack. When implementing custom agents, reference both the server source and this client wrapper to validate payload shapes.

Summary

  • Runtime state (GET /agents/:id/runtime-state) is the single source of truth for agent heartbeat metadata, defined in server/src/services/heartbeat.ts
  • Task sessions (GET /agents/:id/task-sessions and POST .../reset-session) isolate long-running work from heartbeat pings
  • Scheduler heartbeats (GET /instance/scheduler-heartbeats) provides admin visibility across all tenants
  • Control operations (pause, resume, clear-error) modify runtime behavior without changing configuration
  • All timestamps are ISO-8601 strings, all payloads are JSON, and all endpoints live under /api

Frequently Asked Questions

What determines if an agent's scheduler is considered active?

The schedulerActive field in RuntimeState is true only when enabled === true and the status allows scheduling (typically "idle" or "running"). A paused, errored, or explicitly disabled agent will show schedulerActive: false even if the heartbeat interval is configured.

How do task sessions differ from heartbeat reports?

A heartbeat report is a periodic ping that updates lastHeartbeatAt. A task session is a durable scope of work with its own sessionId, status, and lastSeenAt timestamp. One agent may have zero or multiple active sessions while maintaining a single heartbeat stream. Sessions survive transient network failures; heartbeats detect them.

Can I reset a task session without specifying a taskKey?

Yes. The POST /agents/:id/runtime-state/reset-session endpoint accepts an empty body to simply clear any existing session. Omitting taskKey is useful for recovery scenarios where you want to force the agent into a clean state without binding it to new work immediately.

Where is the API contract formally documented?

The OpenAPI specification is generated from server/src/routes/openapi.ts. This file aggregates route definitions and produces machine-readable documentation for all heartbeat and task session endpoints. For implementation details, examine server/src/routes/agents.ts for route handlers and server/src/services/heartbeat.ts for core logic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →