Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.peaq.xyz/llms.txt

Use this file to discover all available pages before exploring further.

The orchestration surface is what makes a service execute end to end. Native skills run through registered runtime endpoints. Machine-side runtime agents register and heartbeat their own endpoints. Tasks model a discover-resolve-execute lifecycle and emit Routes, Outcomes, and Runs. Policies enforce cross-cutting rules. Audit events log every state change. See the Machine Markets overview for base path, auth model, and common envelopes. All endpoints below require the machine to be active and identity-verified. Errors MACHINE_NOT_ACTIVE and MACHINE_NOT_ACTIVATED are uniform across the group.

Runtime endpoints

GET /machines/:machineId/runtime-endpoints

type ListRuntimeEndpointsResponse = ListResponse<RuntimeEndpointView>;

GET /machines/:machineId/runtime-endpoints/:providerKey

type GetRuntimeEndpointResponse = ItemResponse<RuntimeEndpointView>;

PUT /machines/:machineId/runtime-endpoints/:providerKey

Upsert a manual runtime endpoint. source is forced to manual. Returns 201 on create, 200 on update. The server lowercases providerKey in the path.
type UpsertRuntimeEndpointRequest = {
  endpointBaseUrl: string;          // http/https URL, path/query/hash stripped
  label?: string;                   // default `${providerKey.toUpperCase()} runtime`
  authType?: "none" | "api-key" | "bearer";
  authToken?: string;
  modelAlias?: string;
  networkScope?: "loopback" | "lan" | "vpn" | "public";
  probePath?: string;               // default "/v1/models" for qvac, else "/health"
  status?: "active" | "degraded" | "unreachable";
  leaseExpiresAt?: string;          // ISO 8601
};

type UpsertRuntimeEndpointResponse = ItemResponse<RuntimeEndpointView>;
Errors: VALIDATION_ERROR (invalid URL).

DELETE /machines/:machineId/runtime-endpoints/:providerKey

Returns 204. Errors: NOT_FOUND.

RuntimeEndpointView

The view never echoes authToken. Use hasAuthToken to check whether one is stored.
type RuntimeEndpointView = {
  id: string;
  machineId: string;
  providerKey: string;
  source: "manual" | "agent";
  agentId: string | null;
  label: string;
  endpointBaseUrl: string;
  authType: "none" | "api-key" | "bearer";
  hasAuthToken: boolean;
  modelAlias: string | null;
  networkScope: "loopback" | "lan" | "vpn" | "public";
  probePath: string;
  status: "active" | "degraded" | "unreachable";
  leaseExpiresAt: string | null;
  lastCheckedAt: string | null;
  lastHealthyAt: string | null;
  createdAt: string;
  updatedAt: string;
};

Machine-side agents

POST /machines/:machineId/agents/enrollment

Enrols a new runtime agent. Returns the agent record plus a one-time agentToken.
type EnrollMachineAgentRequest = {
  label?: string;                   // default "Machine runtime agent"
  allowedProviderKeys?: string[];   // default ["qvac"], normalised to lowercase
  providerKeys?: string[];          // alias for allowedProviderKeys
};

type EnrollMachineAgentResponse = {
  item: MachineAgentView & { agentToken: string };
};

POST /machine-agents/heartbeat

Heartbeat from an on-machine runtime agent. Auth via agentToken body field or x-agent-pairing-token / Authorization: Bearer header. Upserts one or many endpoints with source: "agent" and a lease.
type HeartbeatRequest = {
  machineId: string;
  agentId: string;
  agentToken?: string;
  leaseSeconds?: number;            // [30, 86400], default 120
  endpoints?: Array<{
    providerKey: string;
    endpointBaseUrl: string;
    label?: string;
    authType?: "none" | "api-key" | "bearer";
    authToken?: string;
    modelAlias?: string;
    networkScope?: "loopback" | "lan" | "vpn" | "public";
    probePath?: string;
    status?: "active" | "degraded" | "unreachable";
  }>;
};

type HeartbeatResponse = {
  item: MachineAgentView;
  endpoints: RuntimeEndpointView[];
};
Errors: VALIDATION_ERROR, NOT_FOUND, AGENT_PAIRING_INACTIVE (status revoked), AGENT_AUTH_REQUIRED, AGENT_AUTH_INVALID, AGENT_POLICY_DENIED (provider key not in agent.allowedProviderKeys). Side effects: agent status flips to active, lastSeenAt updates, each endpoint upserts with leaseExpiresAt = now + leaseSeconds.

MachineAgentView

type MachineAgentView = {
  id: string;
  machineId: string;
  label: string;
  allowedProviderKeys: string[];
  status: "provisioned" | "active" | "revoked";
  hasAgentToken: boolean;
  lastSeenAt: string | null;
  createdAt: string;
  updatedAt: string;
};

Graph

GET /machines/:machineId/graph

type GetGraphResponse = {
  nodes: GraphNodeRecord[];
  edges: GraphEdgeRecord[];
};

POST /machines/:machineId/graph/nodes

type CreateGraphNodeRequest = {
  type: NodeType;
  title: string;
  refId?: string;
  position?: { x?: number; y?: number };
  data?: Record<string, unknown>;
};

type CreateGraphNodeResponse = ItemResponse<GraphNodeRecord>;

PATCH /machines/:machineId/graph/nodes/:nodeId

All fields optional; data is shallow-merged. Status accepted: "draft" | "active" | "blocked" | "degraded".

DELETE /machines/:machineId/graph/nodes/:nodeId

Returns 204.

POST /machines/:machineId/graph/edges

type CreateGraphEdgeRequest = {
  sourceNodeId: string;
  targetNodeId: string;
  type: EdgeType;
  label?: string;
  data?: Record<string, unknown>;
};

type CreateGraphEdgeResponse = ItemResponse<GraphEdgeRecord>;

PATCH /machines/:machineId/graph/edges/:edgeId / DELETE /machines/:machineId/graph/edges/:edgeId

Standard patch and delete semantics.

Graph types

type NodeType =
  | "machine" | "trigger" | "skill" | "task" | "policy" | "discovery"
  | "storage" | "memory" | "handoff" | "route" | "outcome" | "catalog-entry";

type EdgeType =
  | "connects" | "request" | "discover" | "constrain" | "candidate"
  | "route" | "result" | "route-select" | "route-source" | "execute"
  | "handoff" | "receipt" | "sync" | "allow" | "deny";

type GraphNodeRecord = {
  id: string;
  machineId: string;
  type: NodeType;
  refId?: string | null;
  title: string;
  status: "draft" | "active" | "blocked" | "degraded";
  position: { x: number; y: number };
  data: Record<string, unknown>;
  createdAt: string;
  updatedAt: string;
};

type GraphEdgeRecord = {
  id: string;
  machineId: string;
  sourceNodeId: string;
  targetNodeId: string;
  type: EdgeType;
  label?: string;
  data: Record<string, unknown>;
  createdAt: string;
  updatedAt: string;
};

Tasks

Tasks model the discover-resolve-execute pipeline that orders ride on. POST /market/orders materialises an order as a task internally, but tasks can also be driven directly.

GET /machines/:machineId/tasks

type ListTasksResponse = ListResponse<TaskRecord>;

POST /machines/:machineId/tasks

type CreateTaskRequest = {
  serviceType: ServiceType;
  taskType: string;
  operation?: string;
  input?: Record<string, unknown>;
  constraints?: {
    region?: string;
    budget?: number;                 // >= 0
    allowExternalHandoff?: boolean;  // default true
    requireNativeExecution?: boolean;// default false
    requiredCapabilities?: string[];
    preferredSkillKeys?: string[];   // must all be known skills
  };
  discovery?: { maxCandidates?: number };  // 1..25, default 10
};

type CreateTaskResponse = ItemResponse<TaskRecord>;

GET /machines/:machineId/tasks/:taskId

type GetTaskResponse = ItemResponse<TaskRecord>;

POST /machines/:machineId/tasks/:taskId/discover

Runs discovery against the catalogue for a queued task.
type DiscoverTaskRequest = {
  allowExternalHandoff?: boolean;
  requireNativeExecution?: boolean;
  maxCandidates?: number;
};

type DiscoverTaskResponse = {
  taskId: string;
  machineId: string;
  resolution: TaskResolution;
  candidates: Candidate[];
};

POST /machines/:machineId/tasks/:taskId/resolve

Picks a route and persists it. Returns 201.
type ResolveTaskResponse = {
  item: RouteRecord;
  resolution: TaskResolution;
};

POST /machines/:machineId/tasks/:taskId/execute

Executes the resolved route. HTTP status propagates from the inner execution result.
type ExecuteTaskRequest = {
  input?: Record<string, unknown>;
};

type ExecuteTaskResponse = {
  item: OutcomeRecord;
  run: RunRecord;
  resolution: TaskResolution;
};
Errors: ROUTE_REQUIRED (no route resolved), plus all downstream skill-runtime errors.

Task types

type ExternalHandoff = {
  label: string;
  url: string;
  notes: string[];
};

type Candidate = {
  service: CanonicalService;        // catalogue service shape, see machine-markets-discovery
  score: number;
  reasons: string[];
};

type TaskResolution =
  | { kind: "native"; candidate: Candidate }
  | { kind: "handoff"; candidate: Candidate; handoff: ExternalHandoff }
  | { kind: "unavailable"; reason: string; candidates: Candidate[] };

type TaskStatus =
  | "draft" | "queued" | "resolving" | "resolved"
  | "executing" | "completed" | "failed" | "handed_off";

type TaskRecord = {
  id: string;
  machineId: string;
  serviceType: ServiceType;
  operation?: string;
  taskType: string;
  status: TaskStatus;
  input: Record<string, unknown>;
  constraints: {
    region?: string;
    budget?: number;
    allowExternalHandoff: boolean;
    requireNativeExecution: boolean;
    requiredCapabilities: string[];
    preferredSkillKeys?: string[];
  };
  discovery: { maxCandidates: number };
  routeId?: string | null;
  outcomeId?: string | null;
  createdAt: string;
  updatedAt: string;
};

Routes, outcomes, runs

GET /machines/:machineId/routes

type ListRoutesResponse = ListResponse<RouteRecord>;

GET /machines/:machineId/outcomes

type ListOutcomesResponse = ListResponse<OutcomeRecord>;

GET /runs?machineId={id}&...

type ListRunsQuery = {
  machineId: string;
  status?: RunStatus;
  skillKey?: string;
  serviceId?: string;
  executionMode?: ExecutionMode;
  limit?: number;        // default 100, max 500
};

type ListRunsResponse = ListResponse<RunRecord>;

GET /runs/:runId

type GetRunResponse = {
  run: RunRecord;
  outcome: OutcomeRecord | null;
};

Route / Outcome / Run types

type RouteStatus = "selected" | "blocked" | "handed_off" | "failed" | "completed";
type OutcomeStatus = "pending" | "running" | "completed" | "failed" | "external";
type RunStatus = "queued" | "running" | "completed" | "failed" | "external" | "blocked";

type RouteRecord = {
  id: string;
  machineId: string;
  taskId: string;
  selectedSkillKey: string;
  selectedServiceId: string;
  executionMode: ExecutionMode;
  integrationStatus: IntegrationStatus;
  status: RouteStatus;
  reason: string;
  score: number;
  fallbackChain: Array<{ skillKey: string; serviceId: string; executionMode: ExecutionMode }>;
  handoff: ExternalHandoff | null;
  createdAt: string;
  updatedAt: string;
};

type OutcomeRecord = {
  id: string;
  machineId: string;
  taskId: string;
  routeId: string;
  status: OutcomeStatus;
  skillKey: string;
  serviceId: string;
  executionMode: ExecutionMode;
  receiptRef?: string | null;
  logsRef?: string | null;
  externalRef?: string | null;
  errorCode?: string | null;
  errorMessage?: string | null;
  result?: unknown;
  createdAt: string;
  updatedAt: string;
};

type RunRecord = {
  id: string;
  machineId: string;
  taskId: string;
  routeId?: string | null;
  outcomeId?: string | null;
  status: RunStatus;
  executionMode?: ExecutionMode | null;
  skillKey?: string | null;
  serviceId?: string | null;
  summary?: string | null;
  createdAt: string;
  updatedAt: string;
};

Policies

Policies enforce cross-cutting rules above and beyond a pairing’s delegation policy.

GET /policies

type ListPoliciesResponse = ListResponse<PolicyRecord>;

POST /policies

type CreatePolicyRequest = {
  name: string;
  allowedSkillKeys?: string[];
  deniedSkillKeys?: string[];
  budgetCap?: { amount: number; currency: string } | null;
  regionRules?: string[];
  nativeOnly?: boolean;
  handoffAllowed?: boolean;
  requiredCapabilities?: string[];
};

type CreatePolicyResponse = ItemResponse<PolicyRecord>;

GET /policies/:policyId / PATCH /policies/:policyId

Standard get and patch semantics.

PolicyRecord

type PolicyRecord = {
  id: string;
  name: string;
  allowedSkillKeys: string[];
  deniedSkillKeys: string[];
  budgetCap?: { currency: string; amount: number } | null;
  regionRules: string[];
  nativeOnly: boolean;
  handoffAllowed: boolean;
  requiredCapabilities: string[];
  createdAt: string;
  updatedAt: string;
};

Audit events

GET /audit-events

type ListAuditEventsQuery = {
  type?: string;
  resourceType?: string;
  resourceId?: string;
  limit?: number;        // default 100, max 500
};

type ListAuditEventsResponse = ListResponse<AuditEventRecord>;

type AuditEventRecord = {
  id: string;
  actor: string;          // "api" | "system:machine-markets"
  type: string;
  resourceType: string;
  resourceId: string;
  payload: Record<string, unknown>;
  createdAt: string;
};
Event types emitted by the orchestration surface: runtime-endpoint.upserted, runtime-endpoint.deleted, machine-agent.enrolled, machine-agent.heartbeat, graph.node.created, graph.node.updated, graph.node.deleted, graph.edge.created, graph.edge.updated, graph.edge.deleted, task.created, policy.created, policy.updated, market.order.created, market.payment.intent-created, market.payment.proof-recorded, market.order.executed, market.order.confirmed, market.order.disputed, market.payment.escrow-held, market.payment.released, market.payment.refunded.