<!--
Sitemap:
- [Newton Agent NeoBank](/index)
- [Getting Started](/getting-started)
- [Onboard an Agent](/guides/onboard-an-agent)
- [Give an Agent a Budget](/guides/agent-budget)
- [Pay for an API](/guides/pay-for-an-api)
- [Issue a Card](/guides/issue-a-card)
- [Meter Your API](/guides/meter-your-api)
- [Handle Approvals](/guides/handle-approvals)
- [Listen for Events](/guides/listen-for-events)
- [SDK Essentials](/reference/sdk)
- [ModularVault](/reference/sdk/modular-vault)
- [PlatformClient](/reference/sdk/platform-client)
- [FacilitatorClient](/reference/sdk/facilitator-client)
- [createX402Fetch](/reference/sdk/create-x402-fetch)
- [buildPolicyParams](/reference/sdk/build-policy-params)
- [HTTP API](/reference/http-api)
- [Networks & Addresses](/reference/networks)
-->

# PlatformClient

The HTTP client for the hosted platform API. Use it to manage agents and their signing sessions, read vault data, set policy, and run the card rail — all with an operator API key, no local private key required.

```typescript
import { PlatformClient } from "@newton-xyz/isaac";

const platform = new PlatformClient({
  baseUrl: "https://api.example.com",
  apiKey: "nab_sk_...", // operator API key
});
```

You can also get a pre-configured instance from a managed vault with `vault.getPlatformClient()`.

## Agents

```typescript
const { address, sessionExpiresAt } = await platform.createAgent("research-bot");
const agents = await platform.listAgents();
const agent = await platform.getAgent(address);
await platform.refreshSession(address); // extend the signing session
await platform.deactivateAgent(address);
```

`createAgent(name)` provisions a managed key and an initial signing session. `refreshSession(address)` extends a session before it expires; `getAgent`/`listAgents` return the agent's `name`, `hdIndex`, `status`, and `sessionExpiresAt`.

### Signing

```typescript
const { signature } = await platform.sign(address, "message", "hello");
```

`sign(address, type, payload)` signs with the agent's managed key. `type` is `"transaction"`, `"message"`, or `"typed_data"`.

## Vault reads

```typescript
const vaults = await platform.listVaults();
const balance = await platform.getVaultBalance(vaultAddress, [tokenAddress]);
const fleet = await platform.getVaultAgents(vaultAddress);
const history = await platform.getVaultHistory(vaultAddress);
const spending = await platform.getVaultSpending(vaultAddress, "day");
const token = await platform.getTokenMetadata(tokenAddress); // symbol, decimals
```

## Policy

Policy is per-agent. Set up an agent's policy module, then update its params.

```typescript
await platform.setupPolicy(vaultAddress, agentAddress, "token");
const policy = await platform.getAgentPolicy(vaultAddress, agentAddress);
await platform.updatePolicyParams(
  vaultAddress,
  agentAddress,
  params,
  policy.policyId,
  policy.module,
  policy.expireAfter,
);
```

Build `params` with [buildPolicyParams](/reference/sdk/build-policy-params). `setupPolicy` requires the agent's module to already be installed on the vault.

## Smart-payer sessions

Use these methods after an operator allows a discovered service for an agent and deploys an isolated `SessionBudgetSmartPayer`.

```typescript
const session = await platform.createSmartPayerSession(vaultAddress, agentAddress, {
  serviceAuthorizationId,
  expectedBindingFingerprint,
  payerAddress,
  token,
  recipient,
  maxAmount: "1000000",
  validAfter,
  validBefore,
  sessionId,
  authorizedSessionSigner,
  refundRecipient,
});

const sessions = await platform.listSmartPayerSessions(vaultAddress, agentAddress, {
  limit: 20,
  offset: 0,
});
const sameSession = await platform.getSmartPayerSession(
  vaultAddress,
  agentAddress,
  session.id,
);
```

`createSmartPayerSession` records one immutable session term set for the deployed payer. The platform binds the session to the service authorization's recipient, token, network, and binding fingerprint. The SDK x402 smart-payer helper currently consumes Base Sepolia sessions (`eip155:84532`).

```typescript
interface CreateSmartPayerSessionRequest {
  serviceAuthorizationId: string;
  expectedBindingFingerprint: string;
  payerAddress: Address;
  token: Address;
  recipient: Address;
  maxAmount: string;
  validAfter: number;
  validBefore: number;
  sessionId: Hex;
  authorizedSessionSigner: Address;
  refundRecipient: Address;
}

interface SmartPayerSession {
  id: string;
  vaultAddress: string;
  agentAddress: string;
  serviceAuthorizationId: string;
  serviceId: string;
  network: string;
  bindingFingerprint: string;
  payerAddress: Address;
  token: Address;
  recipient: Address;
  maxAmount: string;
  validAfter: number;
  validBefore: number;
  sessionId: Hex;
  authorizedSessionSigner: Address;
  refundRecipient: Address;
  chainId: number;
  status: "registered" | "canceled" | string;
  lifecycleStatus: "registered" | "canceled" | "expired" | string;
  createdAt: string;
  updatedAt: string;
}
```

## Skill runtime

`PlatformClient` inherits scoped discovery plus exactly six skill-runtime methods:

```typescript
const scope = { vaultAddress, agentAddress };
const services = await platform.searchSkillServices(
  scope,
  { q: "research", page: 1, limit: 10 },
);
const service = services.services[0];
if (!service) throw new Error("No executable skill service matched this scope");

const run = await platform.fetchSkill(
  scope,
  {
    serviceId: service.id,
    operation: "search",
    input: { query: "Newton" },
    idempotencyKey: "fetch:2026-07-17:001",
    maxAmountAtomic: "7000",
  },
);

const research = await platform.startSkillResearch(scope, {
  objective: "Compare the available evidence",
  sources: [{ serviceId: service.id, operation: "search", input: { query: "Newton" } }],
  idempotencyKey: "research:2026-07-17:001",
  maxTotalAmountAtomic: "7000",
});
const current = await platform.getSkillRun(scope, research.id); // GET polling
if (current.status === "succeeded" && current.artifactId) {
  const artifact = await platform.getSkillArtifact(scope, current.artifactId);
  const createdExport = await platform.createSkillExport(scope, artifact.id, {
    format: "markdown",
    idempotencyKey: "export:2026-07-17:001",
  });
  await platform.getSkillExport(scope, createdExport.id);
}
```

`searchSkillServices` calls `GET /vaults/{vault}/agents/{agent}/skill-services`. It returns persisted `external-x402` services whose owned authorization, unique registered smart-payer session, current binding, configured-chain canonical Circle USDC terms, and stored caps are execution-eligible. It is not a live endpoint-reachability, quote, or Vault-funding guarantee; those checks occur when a run invokes the service.

The six runtime methods map to `POST /vaults/{vault}/agents/{agent}/skill-runs/fetch`, `POST /vaults/{vault}/agents/{agent}/skill-runs/research`, `GET /vaults/{vault}/agents/{agent}/skill-runs/{runId}`, `GET /vaults/{vault}/agents/{agent}/skill-artifacts/{artifactId}`, `POST /vaults/{vault}/agents/{agent}/skill-artifacts/{artifactId}/exports`, and `GET /vaults/{vault}/agents/{agent}/skill-exports/{exportId}`. Fetch/research require stable idempotency plus an explicit positive atomic-USDC cap; research accepts one to eight sources returned by scoped search. Poll a `SkillRun` until `succeeded` or `failed`, then retrieve its typed artifact and `skill-provenance.v1` provenance. Exports are persisted inline `json`, `markdown`, or `csv` data and accept no destination.

Strict request schemas reject caller URL, network, asset, `payTo`, payer, signer, session, signature, token, and payment-authority fields. Compact server serialization caps each input at 16,384 bytes; raw paid child responses at 256 KiB; artifacts at 16 MiB; and rendered exports at 32 MiB. Oversize or otherwise invalid upstream responses can still fail a run.

HTTP failures surface as `SkillRuntimeError` with exact `code`, `message`, and `httpStatus`. Only `internal` sets `retryable: true`; all other skill-runtime error codes are non-retryable.

## Discovery and service allowlist

Discovery returns the service catalog using the backend contract's snake\_case fields. Undefined filters are omitted from the query string.

```typescript
const discovery = await platform.listDiscoveryServices({
  source: "marketplace",
  trust_tier: "verified",
  category: "api",
  q: "weather",
  page: 1,
  limit: 24,
});

const [service] = discovery.services;
console.log(service.pay_to, service.trust_tier);
```

Preview a catalog service before adding it to an agent's allowlist:

```typescript
const preview = await platform.previewServiceAllowlist(vaultAddress, agentAddress, {
  service_id: service.id,
  token: tokenAddress,
});

console.log(preview.pay_to, preview.amount, preview.binding_fingerprint);
```

Adding the service is an operator-session write with a different auth boundary: the backend route is JWT-only (`AuthUser`, not `DualAuth`) because it mints operator-signable calldata, and it rejects `nab_sk_` API keys by construction. It is therefore **not** on `PlatformClient` — use `PlatformOperatorClient` with the vault owner's operator-session JWT. That JWT may be issued by `POST /auth/siwe/verify` or, for an existing operator with a previously linked email, `POST /auth/email/login/verify`. OTP verification does not perform initial onboarding or directly return the initial API key, but the resulting JWT has the same `AuthUser` authority, including API-key management.

Call it after the operator confirms the host, recipient, canonical base-unit amount, binding fingerprint, and spend caps. Caps are human decimal strings — the server resolves the token's on-chain decimals and converts to base units itself. The response includes an unsigned transaction for the operator to sign and submit.

```typescript
import { PlatformOperatorClient } from "@newton-xyz/isaac";

const operator = new PlatformOperatorClient({
  baseUrl: "https://api.example.com",
  sessionJwt, // operator-session JWT — never a nab_sk_ API key
});

const { unsigned_tx, base_policy_id, authorization_id } =
  await operator.addServiceAllowlist(vaultAddress, agentAddress, {
    service_id: service.id,
    token: tokenAddress,
    max_daily_spend: "100", // human decimal string, e.g. 100 USDC
    max_per_tx: "10",
    acknowledged_host: preview.host,
    expected_pay_to: preview.pay_to,
    expected_amount: preview.amount,
    expected_binding_fingerprint: preview.binding_fingerprint,
    acknowledged: true, // required for untrusted tiers
  });
```

See the [HTTP API reference](/reference/http-api) for route and field ownership.

## Admin transactions

These return an unsigned transaction for you to sign and submit — the platform never holds the operator key.

```typescript
const tx = await platform.withdrawTx(vaultAddress, tokenAddress, "500000", recipient);
await platform.shutdownTx(vaultAddress);
await platform.unpauseTx(vaultAddress);
```

## Card rail

Each method takes the vault address and a body object; `simulateCardAuth` also takes the card token.

```typescript
await platform.linkCardProgram(vaultAddress, {
  program_id: "...",
  api_key: "...",
});

const card = await platform.createCard(vaultAddress, {
  agent_address: agentAddress, // optional — bind the card to an agent
});

await platform.setCardPolicy(vaultAddress, {
  card_policy_params: policy,
});

// Sandbox only — simulate an authorization against an issued card.
const auth = await platform.simulateCardAuth(vaultAddress, cardToken, {
  amount: 1000,
  descriptor: "Coffee Shop",
});
```

See [Issue a Card](/guides/issue-a-card) for the full flow.

## See also

* [ModularVault](/reference/sdk/modular-vault) — connect to a vault and read balances.
* [createX402Fetch](/reference/sdk/create-x402-fetch) — pay 402-gated endpoints or build smart-payer payloads.
* [FacilitatorClient](/reference/sdk/facilitator-client) — charge agents to call your API.
