Skip to main content
The JS/TS exposes the Machine Markets API as a typed namespace on the existing PeaqosClient. Same client, additive surface. The flat method layout matches the HTTP API one-to-one.

Setup

import 'dotenv/config';
import { PeaqosClient } from "@peaqos/peaq-os-sdk";

const client = PeaqosClient.fromEnv();
const { items } = await client.orchestration.listMachines({ limit: 20 });
Touching client.orchestration without orchestrationUrl configured throws OrchestrationConfigError. The namespace lazy-initialises and caches per client.

Configuration

FieldEnv var (via fromEnv())Notes
orchestrationUrlPEAQOS_ORCHESTRATION_URLRequired to use the namespace. Must parse as http: or https:. Non-loopback http:// triggers a warning.
apiKeyPEAQOS_API_KEYRequired for platform-auth calls. Sent as x-api-key.
Constructor form when not using env:
new PeaqosClient({
  rpcUrl,
  privateKey,
  contracts,
  orchestrationUrl: "https://orchestration.peaq.xyz",
  apiKey: "...",
});
Transport constants:
  • API_BASE_PATH = "/api/v1", prepended to every path.
  • Timeouts: 30 s GET, 60 s POST/PATCH/PUT/DELETE.
  • User-Agent: peaq-os-sdk-js/<version>.
No retry, no exponential backoff. Six auto-paginated iterators are available; see Pagination below.

Common envelopes

type ListResponse<T> = {
  readonly items: readonly T[];
  readonly nextCursor?: string;          // omitted when there is no next page
};

type ItemResponse<T> = {
  readonly item: Readonly<T>;
};

type ListQuery = {
  readonly limit?: number;               // 1..500, default 100
  readonly cursor?: string;              // opaque, omit on first page
};
204 No Content resolves to void. validateListQuery rejects limit < 1 or limit > 500 with OrchestrationValidationError before the HTTP call.

Machines

client.orchestration.listMachines(options?: {
  cursor?: string;
  limit?: number;
}): Promise<ListResponse<Machine>>

client.orchestration.createMachine(params: CreateMachineRequest)
  : Promise<ItemResponse<Machine>>

client.orchestration.getMachine(machineId: string)
  : Promise<ItemResponse<Machine>>

client.orchestration.updateMachine(machineId: string, params: UpdateMachineRequest)
  : Promise<ItemResponse<Machine>>

client.orchestration.archiveMachine(machineId: string): Promise<void>
Machine shape:
type Machine = {
  readonly id: string;
  readonly displayName: string;
  readonly status: "draft" | "active" | "degraded" | "blocked" | "archived";
  readonly ownerId: string;
  readonly identityRef?: string | null;
  readonly identityProof?: {
    readonly method: "eip191";
    readonly identityRef: string;
    readonly signerAddress: string;
    readonly challengeId: string;
    readonly verifiedAt: string;
    readonly challengeExpiresAt: string;
    readonly controllerAddresses: readonly string[];
    readonly resolutionSource: "peaqos-mcr";
  } | null;
  readonly machineType: string;
  readonly runtimeProfile: string;
  readonly capabilities: readonly string[];
  readonly labels: Readonly<Record<string, string>>;
  readonly policyIds: readonly string[];
  readonly skillKeys: readonly string[];
  readonly createdAt: string;
  readonly updatedAt: string;
};

Machine identity challenges

client.orchestration.createMachineIdentityChallenge(
  params: CreateMachineIdentityChallengeRequest,
): Promise<ItemResponse<MachineIdentityChallengeResponse>>
Requests a server-issued challenge tied to a did:peaq:0x... or peaqos:machine:<id> identity reference. The DID controller signs item.message with EIP-191 personal_sign and submits the resulting { challengeId, signature } as identityProof when creating or updating the machine record.

Agent pairings

client.orchestration.listAgentPairings(machineId: string, options?: ListAgentPairingsOptions)
  : Promise<ListResponse<AgentPairing>>

client.orchestration.createAgentPairingChallenge(
  machineId: string,
  params: CreateAgentPairingChallengeRequest,
): Promise<ItemResponse<AgentPairingChallengeResponse>>

client.orchestration.createAgentPairing(
  machineId: string,
  params: CreateAgentPairingRequest,
): Promise<ItemResponse<AgentPairing & { pairingToken: string }>>

client.orchestration.createAgentPairingSession(
  machineId: string,
  pairingId: string,
  params: CreateAgentPairingSessionRequest,
): Promise<ItemResponse<AgentPairing & { pairingToken: string }>>

client.orchestration.updateAgentPairing(
  machineId: string,
  pairingId: string,
  params: UpdateAgentPairingRequest,
): Promise<ItemResponse<AgentPairing>>

client.orchestration.revokeAgentPairing(machineId: string, pairingId: string)
  : Promise<void>
Pairing is challenge-based end to end:
  1. createAgentPairingChallenge returns a server-issued challenge keyed to agentAddress, agentProvider, agentRole, and optional agentDid.
  2. The item.message (EIP-191) with the key behind agentAddress.
  3. createAgentPairing accepts the proof in params.agentProof and returns the pairing with a signed HS256 session JWT in pairingToken. The token is returned once at create.
  4. createAgentPairingSession rotates the session token before expiry with a fresh proof. Required after any updateAgentPairing to the , since policy changes invalidate the current token’s delegationPolicyHash.
createAgentPairing and createAgentPairingSession return the pairingToken exactly once each. toJSON redacts the token to "[REDACTED]" so it does not leak through JSON.stringify. CreateAgentPairingRequest now requires agentProof: { challengeId, signature } and accepts optional agentDid. delegationPolicy.allowedServiceIds and delegationPolicy.deniedServiceIds join the existing skill-level allow/deny lists.

Machine agents

client.orchestration.listMachineAgents(machineId: string)
  : Promise<ListResponse<MachineAgent>>

client.orchestration.enrollMachineAgent(
  machineId: string,
  params?: EnrollMachineAgentRequest,
): Promise<ItemResponse<MachineAgent & { provisioningToken: string }>>

client.orchestration.revokeMachineAgent(machineId: string, agentId: string)
  : Promise<void>

client.orchestration.machineAgentHeartbeat(
  params: MachineAgentHeartbeatRequest,
): Promise<MachineAgentHeartbeatResponse>
enrollMachineAgent POSTs /machines/:machineId/agents/enrollment and returns a one-time provisioningToken. machineAgentHeartbeat uses no auth header — the agentToken rides in the request body.

Runtime endpoints

client.orchestration.listRuntimeEndpoints(machineId: string, options?: ListRuntimeEndpointsOptions)
  : Promise<ListResponse<RuntimeEndpoint>>

client.orchestration.getRuntimeEndpoint(machineId: string, providerKey: string)
  : Promise<ItemResponse<RuntimeEndpoint>>

client.orchestration.upsertRuntimeEndpoint(
  machineId: string,
  providerKey: string,
  params: UpsertRuntimeEndpointRequest,
): Promise<ItemResponse<RuntimeEndpoint>>

client.orchestration.deleteRuntimeEndpoint(machineId: string, providerKey: string)
  : Promise<void>
Upsert uses HTTP PUT. endpointBaseUrl is parsed with new URL(...) client-side; invalid URLs throw OrchestrationValidationError(field: "endpointBaseUrl", constraint: "valid URL").

Skills

client.orchestration.listSkills(options?: {
  scope?: SkillScope;
  direction?: SkillDirection;
  source?: SkillSource;
}): Promise<ListResponse<SkillSummary>>

client.orchestration.getSkill(skillKey: string)
  : Promise<ItemResponse<SkillSummary>>

client.orchestration.getSkillManifest(skillKey: string)
  : Promise<ItemResponse<SkillManifest>>

client.orchestration.updateSkillConfig(
  skillKey: string,
  params: UpdateSkillConfigRequest,
): Promise<ItemResponse<SkillConfigResponse>>

Market services

client.orchestration.listMarketServices(options?: {
  machineId?: string;
  serviceType?: ServiceType;
  executionMode?: ExecutionMode;
  providerKey?: string;
}): Promise<ListResponse<MarketService>>

client.orchestration.getMarketService(
  serviceId: string,
  options?: { machineId?: string },
): Promise<ItemResponse<MarketService>>
client.orchestration.searchMarket(
  params: MarketSearchRequest,
  pairingToken: string,
): Promise<ItemResponse<MarketSearch>>

client.orchestration.getMarketSearch(searchId: string)
  : Promise<ItemResponse<MarketSearch>>
searchMarket uses x-agent-pairing-token auth — the token rides in the second argument and is sent per call. getMarketSearch falls back to platform auth.

Market orders

client.orchestration.listMarketOrders(
  options: ListMarketOrdersOptions,        // { machineId, cursor?, limit? }
): Promise<ListResponse<MarketOrder>>

client.orchestration.createMarketOrder(
  params: CreateMarketOrderRequest,        // { machineId, agentPairingId, serviceId, searchId?, quoteId?, operation?, input?, budget?, providerCredentials? }
  pairingToken: string,
): Promise<ItemResponse<MarketOrder>>

client.orchestration.getMarketOrder(
  orderId: string,
): Promise<ItemResponse<MarketOrder>>

client.orchestration.executeMarketOrder(
  orderId: string,
  params: ExecuteMarketOrderRequest,       // { input? }
  pairingToken: string,
): Promise<ExecuteMarketOrderResponse>     // discriminated union

client.orchestration.confirmMarketOrder(
  orderId: string,
  pairingToken: string,
): Promise<ConfirmMarketOrderResponse>     // { order, payment: MarketPayment | null }

client.orchestration.disputeMarketOrder(
  orderId: string,
  params: DisputeMarketOrderRequest,       // { reason, evidence? }
  pairingToken: string,
): Promise<DisputeMarketOrderResponse>     // { order, payment, dispute }; payment → "frozen"
listMarketOrders and getMarketOrder use platform auth; the four mutating calls require the agent’s pairingToken. executeMarketOrder returns a discriminated union on execution.statusCode:
const result = await client.orchestration.executeMarketOrder(orderId, {}, pairingToken);
if (result.execution.statusCode === 200) {
  // Native execution
  const { run, outcome } = result.execution;
} else {
  // 202 — external handoff
  const { handoff } = result.execution;   // { label, url, notes }
}

Payment settlement

client.orchestration.createPaymentIntent(
  orderId: string,
  params: CreatePaymentIntentRequest,      // { rail?, amount?, currency? }
  pairingToken: string,
): Promise<ItemResponse<MarketPayment>>

client.orchestration.getMarketPayment(
  orderId: string,
): Promise<ItemResponse<MarketPayment | null>>  // platform auth; item may be null

client.orchestration.submitPaymentProof(
  orderId: string,
  params: SubmitPaymentProofRequest,       // EVM: transactionHash | Solana: transactionSignature + verificationMode + chain, token, payerAddress, payeeAddress, amount
  pairingToken: string,
): Promise<ItemResponse<MarketPayment>>

client.orchestration.lockEscrowPayment(
  orderId: string,
  params: LockEscrowPaymentRequest,        // { transactionHash, chain, escrowAddress } all required
  pairingToken: string,
): Promise<ItemResponse<MarketPayment>>

client.orchestration.releasePayment(
  orderId: string,
  params: ReleasePaymentRequest | undefined,  // { transactionHash?, notes? }
  pairingToken: string,
): Promise<ItemResponse<MarketPayment>>

client.orchestration.refundPayment(
  orderId: string,
  params: RefundPaymentRequest | undefined,   // { transactionHash?, reason? }
  pairingToken: string,
): Promise<ItemResponse<MarketPayment>>
submitPaymentProof accepts EVM (transactionHash) and Solana (transactionSignature) shapes with verificationMode: "recorded" | "rpc". RPC verification cross-checks the ERC-20 Transfer log against expected token, payerAddress, payeeAddress, and amount.

Pagination

Six auto-paginated iterators ship in this release:
for await (const m of client.orchestration.listMachinesAll()) { /* … */ }
for await (const s of client.orchestration.listSkillsAll()) { /* … */ }
for await (const svc of client.orchestration.listMarketServicesAll()) { /* … */ }
for await (const o of client.orchestration.listMarketOrdersAll({ machineId: "m-1" })) { /* … */ }
for await (const p of client.orchestration.listAgentPairingsAll("m-1")) { /* … */ }
for await (const e of client.orchestration.listRuntimeEndpointsAll("m-1")) { /* … */ }
Each *All accepts Omit<XxxOptions, "cursor"> — cursor is managed internally; filters and limit pass through on every page. For manual pagination, the underlying list* methods now accept cursor and limit:
let cursor: string | undefined;
do {
  const page = await client.orchestration.listMarketOrders({ machineId: "m-1", limit: 50, cursor });
  for (const order of page.items) handle(order);
  cursor = page.nextCursor;             // undefined when terminal
} while (cursor);

Coming next

These endpoints ship on the HTTP API (see Machine Markets API: Orchestration). SDK method bindings follow shortly:
  • Tasks: createTask, discoverTask, resolveTask, executeTask, getTask, listTasks
  • Graph: getMachineGraph, createGraphNode, updateGraphNode, deleteGraphNode, createGraphEdge, updateGraphEdge, deleteGraphEdge
  • Policies: listPolicies, createPolicy, getPolicy, updatePolicy
  • Observability: listAuditEvents, listRuns, getRun, getReadiness, getHealth
  • Order family extras: cancelMarketOrder, retryMarketOrder
Types for policies, observability, and the remaining order-family operations already ship in @peaqos/peaq-os-sdk so application code can prepare for the methods landing. Forward-looking type declarations carry an @experimental JSDoc tag — treat them as wire shapes likely to change. Method-level stubs for not-yet-implemented endpoints (skill submissions, etc.) are intentionally not exported until the endpoints land.

Errors

Five exported classes, all extend PeaqosError:
class OrchestrationError extends PeaqosError {}

class OrchestrationApiError extends OrchestrationError {
  readonly code: string;        // server-provided, e.g. "NOT_FOUND"
  readonly statusCode: number;  // HTTP 4xx/5xx
  readonly details: unknown;    // server-provided detail blob
}

class OrchestrationNetworkError extends OrchestrationError {
  // .cause carries the original transport error
}

class OrchestrationConfigError extends OrchestrationError {}

class OrchestrationValidationError extends OrchestrationError {
  readonly field: string;
  readonly constraint: string;
}
Switch on instanceof OrchestrationApiError then on err.code. Never parse err.message. Server code values exported as constants from @peaqos/peaq-os-sdk: ERROR_CODE_AUTH_REQUIRED, ERROR_CODE_AUTH_INVALID, ERROR_CODE_AGENT_AUTH_REQUIRED, ERROR_CODE_AGENT_AUTH_INVALID, ERROR_CODE_AGENT_AUTH_EXPIRED, ERROR_CODE_VALIDATION_ERROR, ERROR_CODE_NOT_FOUND, ERROR_CODE_MACHINE_NOT_ACTIVE, ERROR_CODE_MACHINE_NOT_ACTIVATED, ERROR_CODE_MACHINE_IDENTITY_EXISTS, ERROR_CODE_MACHINE_IDENTITY_IMMUTABLE, ERROR_CODE_MACHINE_IDENTITY_PROOF_REQUIRED, ERROR_CODE_MACHINE_IDENTITY_PROOF_INVALID, ERROR_CODE_MACHINE_IDENTITY_PROOF_EXPIRED, ERROR_CODE_PEAQOS_IDENTITY_UNAVAILABLE, ERROR_CODE_AGENT_PAIRING_PROOF_REQUIRED, ERROR_CODE_AGENT_PAIRING_PROOF_INVALID, ERROR_CODE_AGENT_PAIRING_PROOF_EXPIRED, ERROR_CODE_AGENT_PAIRING_UNAVAILABLE, ERROR_CODE_AGENT_PAIRING_REQUIRED, ERROR_CODE_AGENT_PAIRING_INACTIVE, ERROR_CODE_AGENT_POLICY_DENIED, ERROR_CODE_AGENT_SPEND_LIMIT_EXCEEDED, ERROR_CODE_AGENT_DAILY_LIMIT_EXCEEDED, ERROR_CODE_QUOTE_EXPIRED, ERROR_CODE_ORDER_CLOSED, ERROR_CODE_ORDER_NOT_DELIVERED, ERROR_CODE_PAYMENT_REQUIRED, ERROR_CODE_PAYMENT_RPC_REQUIRED, ERROR_CODE_PAYMENT_RPC_ERROR, ERROR_CODE_PAYMENT_TX_FAILED, ERROR_CODE_EXECUTION_UNSUPPORTED, ERROR_CODE_ENDPOINT_UNREACHABLE. New types exported in 0.2.0:
type MachineIdentityProofInput = { challengeId: string; signature: string };

type AgentPairingChallengeResponse = {
  challengeId: string;
  agentAddress: string;
  message: string;
  expiresAt: string;
  verificationMethod: "eip191";
};

type AgentPairingProof = { challengeId: string; signature: string };

type ProviderCredentials = {
  // Per-adapter credentials keyed by providerKey. Shape is
  // adapter-specific and documented per-adapter on robotic.sh.
  // Always redacted before request bodies are persisted.
  [providerKey: string]: Record<string, string | undefined>;
};
MarketPaymentRailType adds "wdk-usdt-transfer" alongside the existing "x402" | "mpp" | "wallet" | "vault-stripe" | "escrow" | "onchain-escrow" | "offchain-record" | "external" | "not-required" set. The transport synthesises BAD_RESPONSE (non-JSON error body) and INVALID_RESPONSE_SHAPE (envelope mismatch) inside OrchestrationApiError when the server response is malformed.

Credential handling

Three auth modes per call:
  • Platform (x-api-key) — set once on the client via apiKey, sent on every call by default.
  • Agent pairing (x-agent-pairing-token) — passed per call to searchMarket as the second argument. The SDK does not store or rotate it.
  • None — used only by machineAgentHeartbeat (token rides in the body).
sanitizeBody() redacts any body field whose name contains token, key, secret, password, credential, or auth (case-insensitive) before attaching the body to an error. Redaction is recursive into nested objects.