> ## 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.

# Orchestration (JavaScript)

> client.orchestration.* namespace on the JS/TS SDK — machines, agent pairings, machine agents, runtime endpoints, skills, market services, market search.

The JS/TS <Tooltip tip={G.sdk.def}>SDK</Tooltip> exposes the [Machine Markets API](/peaqos/api-reference/machine-markets-overview) as a typed namespace on the existing `PeaqosClient`. Same client, additive surface. The flat method layout matches the HTTP API one-to-one.

## Setup

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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

| Field              | Env var (via `fromEnv()`)  | Notes                                                                                                        |
| :----------------- | :------------------------- | :----------------------------------------------------------------------------------------------------------- |
| `orchestrationUrl` | `PEAQOS_ORCHESTRATION_URL` | Required to use the namespace. Must parse as `http:` or `https:`. Non-loopback `http://` triggers a warning. |
| `apiKey`           | `PEAQOS_API_KEY`           | Required for platform-auth calls. Sent as `x-api-key`.                                                       |

Constructor form when not using env:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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](#pagination) below.

## Common envelopes

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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 <Tooltip tip={G.machineAgent.def}>Machine Agent</Tooltip> <Tooltip tip={G.sign.def}>signs</Tooltip> `item.message` (EIP-191) with the <Tooltip tip={G.wallet.def}>wallet</Tooltip> 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 <Tooltip tip={G.delegationPolicy.def}>delegation policy</Tooltip>, 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

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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>>
```

## Market search

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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`:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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`:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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](/peaqos/api-reference/machine-markets-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`:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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`:**

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark"}}
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.

## Related

* [Machine Markets API](/peaqos/api-reference/machine-markets-overview)
* [Orchestration (Python)](/peaqos/sdk-reference/orchestration-py)
* [Scale function](/peaqos/functions/scale)
* [Machine Markets concept](/peaqos/concepts/machine-markets)
