Introduction

NoVault is a non-custodial coordination protocol for autonomous agent workflows. It provides a single layer that handles capability discovery, provider selection, payment settlement, and execution verification — so agents can call paid services without holding keys or trusting an operator.

This documentation covers the full developer surface: the TypeScript and Python SDKs, the REST API, the provider schema format, the x402 payment protocol, on-chain escrow contracts, and the ZK verification roadmap.

New here? Jump to Quick Start to make your first request in under three minutes, then come back to Core Concepts for a deeper understanding of how the protocol works.

Architecture at a glance

Every NoVault interaction passes through four cooperating layers. Each layer has a narrow responsibility and communicates through machine-readable schemas.

Intent Layer

Agent declares what it needs in a structured intent object. No provider coupling, no SDK lock-in.

Resolution Layer

Schema registry matches the intent to eligible providers and returns a ranked candidate list.

Settlement Layer

x402 escrow opens, the request executes, and the escrow collapses atomically. No custody.

Verification Layer

A signed receipt attests to every outcome. Disputes trigger deterministic re-execution or ZK proof.

Quick Start

Install the SDK, configure your API key, and execute your first paid agent request in a few lines of code.

Install the SDK

Terminalbash
# npm
npm install @novault/sdk

# pnpm
pnpm add @novault/sdk

# yarn
yarn add @novault/sdk

Configure the client

novault.tstypescript
import { NoVault } from "@novault/sdk";

export const nv = new NoVault({
  apiKey: process.env.NOVAULT_API_KEY!,
  // Optional: pin to testnet during development
  environment: "sandbox",
});

Execute your first intent

example.tstypescript
import { nv } from "./novault";

const result = await nv.intent.execute({
  capability: "image/upscale",
  input: {
    url: "https://example.com/photo.png",
    scale: 4,
  },
  budget: {
    maxUsd: "0.05",
  },
});

console.log(result.output.url);     // → upscaled image URL
console.log(result.receipt.hash);   // → cryptographic execution receipt
The SDK automatically handles provider resolution, escrow, execution, and receipt verification. You interact with a single intent.execute() call.

Verify the receipt

Every response includes a signed receipt. You can verify it independently of the NoVault API at any time.

verify.tstypescript
import { verifyReceipt } from "@novault/sdk";

const isValid = await verifyReceipt(result.receipt);
// → true

Authentication

All API requests authenticate with a bearer token. Agent-side signing uses ed25519 keypairs for intent co-signing; the API key governs dashboard and management access.

API keys

API keys are scoped to a single environment (sandbox or production). Prefix nv_live_ indicates production; nv_test_ indicates sandbox. Never commit keys to source control.

cURLbash
curl https://api.novault.xyz/v1/intents \
  -H "Authorization: Bearer nv_live_sk_…" \
  -H "Content-Type: application/json"

Agent signing keys

Intent co-signing uses a separate ed25519 keypair generated locally by the SDK. The private key never leaves the agent runtime — the API sees only the signed intent payload and the agent's public key DID.

keygen.tstypescript
import { generateAgentIdentity } from "@novault/sdk";

// Run once; persist the identity securely
const identity = await generateAgentIdentity();
// { did: "did:novault:0x…", privateKey: "…", publicKey: "…" }

const nv = new NoVault({
  apiKey: process.env.NOVAULT_API_KEY!,
  agentIdentity: identity,
});
Store the agent private key in a secrets manager (Vault, AWS Secrets Manager, Doppler). Anyone with the private key can co-sign intents up to the budget limits on the associated account.

Scoped API keys

Fine-grained API keys can be restricted by IP CIDR, capability class, maximum per-request spend, and TTL. Create them via the dashboard or the Management API.

ParameterTypeRequiredDescription
capabilitiesstring[]optionalRestrict to specific capability namespaces, e.g. ["image/*", "data/price"]
maxRequestUsdstringoptionalHard cap per single request. Requests exceeding this are rejected before escrow opens.
ipCidrsstring[]optionalAllowlist of IPv4/IPv6 CIDR blocks. Empty = unrestricted.
expiresAtISO 8601optionalKey expiry timestamp. After expiry all requests with this key return 401.

Environments

NoVault runs two isolated environments. Sandbox uses simulated settlement with no real funds; production settles on-chain.

SandboxProduction
Base URLhttps://api.sandbox.novault.xyz/v1https://api.novault.xyz/v1
Key prefixnv_test_nv_live_
SettlementSimulatedOn-chain (USDC)
SlashingNoYes
Rate limit100 req/min1 000 req/min
SLABest-effort99.9% uptime

Intent Declaration

An intent is the fundamental unit of interaction with NoVault. It is a signed, structured object that describes what an agent needs — not which provider to call or how the request should be routed. This separation is the core design principle of the protocol.

Intent anatomy

Intent object (full schema)json
{
  "capability": "image/upscale",          // namespaced capability identifier
  "version": "^1.0",                      // semver range accepted
  "input": {                              // capability-specific input
    "url": "https://…/photo.png",
    "scale": 4
  },
  "budget": {
    "maxUsd": "0.10",                     // hard maximum for this request
    "preferLowestPrice": true             // resolution hint
  },
  "policy": {
    "ttlMs": 8000,                        // abort if not settled within 8 s
    "retries": 2,                         // retry on provider failure
    "requireVerification": true           // reject unverified receipts
  },
  "agentDid": "did:novault:0xabc…",      // agent's public identity
  "nonce": "f3c9…",                       // prevents replay
  "signature": "ed25519:0x7e3f…"         // agent co-signature over intent hash
}

Capability namespaces

Capabilities follow a two-part namespace: category/action. Wildcard matching is used in key scoping but not in intent declarations — every intent must name an exact capability.

  • image/upscale — Image upscaling
  • image/generate — Text-to-image generation
  • data/price — Real-time asset price feed
  • data/risk-score — On-chain risk assessment
  • llm/complete — LLM text completion
  • llm/embed — Text embedding
  • compute/render — GPU render job
Custom capability namespaces can be registered by protocol participants via the Schema Registry. The x- prefix is reserved for experimental or private capabilities.

Capability Resolution

When an intent arrives, the Resolution Layer queries the Schema Registry for providers that satisfy the capability, version range, and input contract. It ranks candidates using a scoring function and returns an ordered list.

Scoring function

Candidates are scored by a weighted combination of four observable signals. Weights can be overridden per-intent via the budget hints object.

Price40%

Lower price scores higher. Normalised across all candidates in the same capability class.

SLA compliance (p99)30%

Rolling 30-day p99 latency vs declared SLA. Providers missing their SLA by >20% are excluded.

Uptime20%

Rolling 7-day availability. Providers below 95% uptime are excluded regardless of other scores.

Schema fit10%

Semantic match between the intent input schema and the provider capability schema.

Resolution hints

Add hints to the budget object to override default weighting for a specific request.

budget: {
  maxUsd: "0.10",
  preferLowestPrice: true,       // weight price 80%
  requireSlaCompliant: true,     // exclude any provider with SLA miss in last 7 days
  pinProvider: "did:novault:0x…" // bypass resolution, pin to specific provider
}

Request Settlement

NoVault settles every service call atomically at the request level. There are no monthly invoices, no batched reconciliation, and no credit risk. The full payment lifecycle for a single request is:

1

Intent lock

Agent co-signs an intent object. No funds move. The lock establishes the agreed provider, maximum price, and TTL.

2

Escrow open

An on-chain escrow contract receives the agreed payment. Neither the agent nor the provider controls it unilaterally.

3

Execution

Provider executes the request and returns output alongside a signed execution receipt.

4

Receipt validation

The validation layer checks the receipt's signature, output hash, and latency.

5

Settlement

If valid: escrow releases payment to provider. If invalid or timed out: agent is refunded; provider stake is slashed proportionally.

x402 payment header

Settlement uses the x402 open standard. The escrow authorization travels as an HTTP header with the request — no separate payment round-trip is needed.

x402 payment headerhttp
X-Payment: {
  "version": "1",
  "scheme": "exact",
  "network": "base-mainnet",
  "maxAmountRequired": "40000",   // in USDC microunits (6 decimals)
  "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "payTo": "0xEscrowContract…",
  "extra": {
    "intentHash": "sha256:3f4a…",
    "agentSignature": "ed25519:0x…"
  }
}

Execution Verification

Every execution receipt is a cryptographically signed attestation that binds the provider identity, request hash, output hash, latency, and timestamp into a single unforgeable object.

Execution receipt (full schema)json
{
  "receiptVersion": "1",
  "intentHash": "sha256:3f4a…",
  "outputHash": "sha256:9c1b…",
  "provider": "did:novault:0xabcd…",
  "capabilityId": "image/upscale",
  "executedAt": 1753996800,
  "settledAt": 1753996802,
  "latencyMs": 1240,
  "amountPaidUsd": "0.0040",
  "verificationMethod": "deterministic-reexecution",
  "verifierNode": "did:novault:0xverif…",
  "signature": "ed25519:0x7e3f…"
}

Verification methods

  • Deterministic re-execution — for deterministic capabilities (data feeds, transforms, validations): a verifier node reruns the request and compares output hashes.
  • Merkle proof — for capabilities with large outputs: a Merkle root of output chunks replaces the full output hash, verified by spot-checking.
  • ZK proof (roadmap) — for non-deterministic capabilities (LLM inference, generative models): ZK proof of correct execution attached to the receipt.
Receipts are stored on IPFS with a content-addressed CID included in the receipt object. You can verify any historical receipt independently using the @novault/sdk verifyReceipt() function or directly against the smart contract.

Receipts & Evidence

Receipts are the accountability primitive of the NoVault protocol. They are returned synchronously with every successful response and stored immutably.

Accessing receipts in the SDK

const result = await nv.intent.execute({ … });

// Receipt is always present on success
const { receipt } = result;

console.log(receipt.hash);            // content-addressed receipt ID
console.log(receipt.amountPaidUsd);   // exact amount settled
console.log(receipt.latencyMs);       // provider execution latency
console.log(receipt.verifierNode);    // DID of verifier that checked the output

// Independently verify the receipt at any time
const valid = await nv.receipts.verify(receipt.hash);

Receipt retention

Receipts are permanently stored on IPFS and indexed by the NoVault API for 90 days with fast retrieval. After 90 days, retrieval falls back to IPFS CID resolution. On-chain settlement events are permanently accessible on-chain.

Querying receipts

// List receipts for an agent
const receipts = await nv.receipts.list({
  agentDid: "did:novault:0xabc…",
  capability: "image/upscale",
  from: "2026-01-01",
  to: "2026-08-01",
  limit: 50,
});

// Fetch a single receipt
const receipt = await nv.receipts.get("sha256:9c1b…");

Making Your First Request

This guide walks through a complete end-to-end request using the TypeScript SDK, from client setup to receipt verification.

complete-example.tstypescript
import { NoVault, generateAgentIdentity, verifyReceipt } from "@novault/sdk";

// 1. Generate a persistent agent identity (do this once, store it securely)
const identity = await generateAgentIdentity();

// 2. Initialise the client
const nv = new NoVault({
  apiKey: process.env.NOVAULT_API_KEY!,
  agentIdentity: identity,
  environment: "sandbox", // switch to "production" when ready
});

// 3. Execute an intent
const result = await nv.intent.execute({
  capability: "data/price",
  input: {
    asset: "ETH",
    currency: "USD",
    sources: ["chainlink", "pyth"],
    aggregation: "median",
  },
  budget: {
    maxUsd: "0.002",
  },
  policy: {
    ttlMs: 5000,
    requireVerification: true,
  },
});

// 4. Use the output
console.log("ETH price:", result.output.price, result.output.currency);

// 5. Verify the receipt
const isValid = await verifyReceipt(result.receipt);
console.log("Receipt valid:", isValid);
console.log("Paid:", result.receipt.amountPaidUsd, "USD");
Set environment: "sandbox" during development. Sandbox requests use simulated settlement — no real funds are moved.

Budget Enforcement

Budget limits are enforced at the protocol level — before escrow opens. An agent cannot overspend; it can only under-spend. Limits compose: per-request, per-session, and capability-class limits all apply simultaneously.

Per-request budget

budget: {
  maxUsd: "0.10",                // hard cap — request rejected if all providers cost more
  preferLowestPrice: true,       // resolution hint: weight price heavily
}

Session-level budget

const session = await nv.sessions.create({
  budget: {
    totalMaxUsd: "5.00",           // session hard cap
    capabilityLimits: {
      "image/*": "2.00",           // wildcard cap for all image capabilities
      "llm/complete": "1.50",
    },
  },
  ttlSeconds: 3600,               // session expires after 1 hour
});

// All intents executed within this session share the budget pool
const result = await nv.intent.execute(
  { capability: "image/upscale", … },
  { sessionId: session.id }
);

Account-level policy

Account-level policies are set in the dashboard and apply to all API keys in the account. They cannot be exceeded by per-request or session budgets.

ParameterTypeRequiredDescription
dailyMaxUsdstringoptionalMaximum total spend per calendar day (UTC). Resets at 00:00 UTC.
monthlyMaxUsdstringoptionalMaximum total spend per calendar month.
maxRequestUsdstringoptionalHard ceiling on any single request, regardless of intent budget.
allowedCapabilitiesstring[]optionalCapability namespaces this account may access. Wildcard matching supported.
blockedProvidersstring[]optionalProvider DIDs that will never be selected for this account.

Registering as a Provider

Any entity can register as a NoVault provider by completing three steps: identity creation, collateral bonding, and schema publication.

Step 1 — Create a provider identity

import { createProviderIdentity } from "@novault/sdk/provider";

const provider = await createProviderIdentity({
  name: "Acme Upscaling Inc.",
  contact: "ops@acme.example",
  website: "https://acme.example",
});
// → { did: "did:novault:0x…", privateKey: "…", publicKey: "…" }

Step 2 — Bond collateral

Providers must bond USDC collateral proportional to their declared throughput. Collateral is slashed on failed receipts.

await nv.provider.bondCollateral({
  providerDid: provider.did,
  amountUsd: "500.00",  // minimum: $100 for <100 req/min; $1 000 for <1 000 req/min
});

Step 3 — Publish your schema

See Publishing a Schema for the full schema format.

Collateral bonds are locked for a minimum of 7 days after unbonding is requested. Providers with active slashing disputes cannot initiate an unbond.

Publishing a Schema

A capability schema is the machine-readable contract that agents use to decide whether a provider can satisfy their intent. It must be accurate — discrepancies between the declared schema and actual behavior are grounds for slashing.

capability-schema.jsonjson
{
  "capability": "image/upscale",
  "version": "1.3.0",
  "provider": "did:novault:0xabcd…",
  "input": {
    "type": "object",
    "properties": {
      "url":   { "type": "string", "format": "uri" },
      "scale": { "type": "integer", "enum": [2, 4, 8] }
    },
    "required": ["url", "scale"]
  },
  "output": {
    "type": "object",
    "properties": {
      "url":    { "type": "string", "format": "uri" },
      "width":  { "type": "integer" },
      "height": { "type": "integer" }
    }
  },
  "pricing": {
    "model": "per-request",
    "unit": "megapixel",
    "rate": "0.0004",
    "currency": "USDC"
  },
  "sla": {
    "p99LatencyMs": 3000,
    "uptimePct": 99.5,
    "maxConcurrentRequests": 200
  },
  "settlement": {
    "method": "x402",
    "payTo": "0xYourWalletAddress…",
    "network": "base-mainnet"
  }
}

Publishing via SDK

import schema from "./capability-schema.json";

await nv.provider.publishSchema({
  providerDid: provider.did,
  schema,
  // SDK validates the schema locally before submitting
});
// → { schemaId: "sha256:…", status: "pending_review" }
// Schemas enter the registry after a 1-hour automated validation pass.

Multi-provider Fallback

Configure automatic fallback so that if the top-ranked provider fails, the intent is retried with the next eligible candidate — without any code changes on your side.

const result = await nv.intent.execute({
  capability: "llm/complete",
  input: { prompt: "Summarise this contract: …", maxTokens: 512 },
  budget: { maxUsd: "0.05" },
  policy: {
    ttlMs: 12000,
    retries: 3,           // try up to 3 additional providers on failure
    retryOnCodes: [       // only retry on these provider error codes
      "PROVIDER_TIMEOUT",
      "PROVIDER_RATE_LIMITED",
      "PROVIDER_UNAVAILABLE",
    ],
  },
});

// The receipt tells you which provider ultimately settled the request
console.log(result.receipt.provider);
console.log(result.metadata.providerAttempts); // → 2 (if first failed)
Retries are transparent: the same intent hash is re-used. You pay only for the attempt that succeeds — failed attempts are not charged.

Webhooks & Events

Subscribe to settlement and lifecycle events via HTTPS webhooks. Events fire for both synchronous and asynchronous intents.

Registering a webhook

await nv.webhooks.create({
  url: "https://your-server.example/novault/events",
  events: [
    "intent.settled",
    "intent.failed",
    "receipt.verified",
    "receipt.disputed",
    "provider.slashed",
  ],
  secret: process.env.WEBHOOK_SECRET!,
});

Verifying webhook signatures

import { verifyWebhookSignature } from "@novault/sdk";

// In your express handler (or equivalent):
app.post("/novault/events", (req, res) => {
  const isValid = verifyWebhookSignature({
    payload: req.rawBody,          // must be the raw buffer, not parsed JSON
    signature: req.headers["x-novault-signature"] as string,
    secret: process.env.WEBHOOK_SECRET!,
  });

  if (!isValid) return res.status(401).send("Invalid signature");

  const event = req.body;
  if (event.type === "intent.settled") {
    console.log("Settled:", event.data.intentHash, event.data.amountPaidUsd);
  }

  res.status(200).send("ok");
});
ParameterTypeRequiredDescription
intent.settledeventoptionalFires when an intent's escrow releases successfully to the provider.
intent.failedeventoptionalFires when all retry attempts for an intent are exhausted.
intent.refundedeventoptionalFires when a receipt fails validation and the agent is refunded.
receipt.verifiedeventoptionalFires when a verifier node confirms a receipt is valid.
receipt.disputedeventoptionalFires when a receipt verification fails and a dispute is opened.
provider.slashedeventoptionalFires when a provider's collateral is slashed due to a failed dispute.

Intents API

POST/v1/intentsExecute an intent

Resolve a capability, open escrow, execute the request, and return settled output and a receipt.

ParameterTypeRequiredDescription
capabilitystringrequiredNamespaced capability identifier, e.g. image/upscale
versionstringoptionalSemver range of acceptable capability schema versions. Defaults to latest stable.
inputobjectrequiredCapability-specific input payload. Shape validated against the resolved provider's schema.
budgetobjectoptionalmaxUsd, preferLowestPrice, requireSlaCompliant, pinProvider.
policyobjectoptionalttlMs, retries, retryOnCodes, requireVerification.
sessionIdstringoptionalAssociate this request with a session budget pool.
agentDidstringoptionalAgent DID for co-signing. Required if agentIdentity is not set on the client.
noncestringoptionalIdempotency nonce. Duplicate nonces within 24 h return the original result without re-execution.

GET/v1/intents/:intentHashGet intent status

Returns current lifecycle status for an intent. Useful for async intents that execute in the background.


GET/v1/intentsList intents
ParameterTypeRequiredDescription
capabilitystringoptionalFilter by capability.
statusstringoptionalpending | executing | settled | failed | refunded
fromISO 8601optionalStart of date range (inclusive).
toISO 8601optionalEnd of date range (inclusive).
limitintegeroptionalMax records per page. Default 20, max 100.
cursorstringoptionalPagination cursor from previous response.

Registry API

The Schema Registry is a permissionless index of provider capability schemas.

GET/v1/registry/capabilitiesList all registered capabilities
cURLbash
curl "https://api.novault.xyz/v1/registry/capabilities?category=image" \
  -H "Authorization: Bearer nv_live_…"

GET/v1/registry/capabilities/:capability/providersList providers for a capability

Returns ranked provider list for a given capability. Useful for inspection without executing an intent.


POST/v1/registry/schemasPublish a new capability schema
ParameterTypeRequiredDescription
providerDidstringrequiredDID of the publishing provider. Must match the signing key.
schemaobjectrequiredFull capability schema object. See Publishing a Schema.
replacesstringoptionalSchema ID this version supersedes. Old version is deprecated, not deleted.

DELETE/v1/registry/schemas/:schemaIdDeprecate a schema

Marks a schema as deprecated. In-flight intents using the schema complete normally; new resolutions exclude it.

Settlement API

GET/v1/settlement/escrows/:intentHashGet escrow state

Returns on-chain escrow state for an intent. Useful for debugging failed settlements.

Responsejson
{
  "intentHash": "sha256:3f4a…",
  "state": "released",              // pending | open | released | refunded | slashed
  "amount": "40000",                // USDC microunits
  "openedAt": 1753996798,
  "settledAt": 1753996802,
  "provider": "did:novault:0xabcd…",
  "txHash": "0xchain…"
}

POST/v1/settlement/disputeOpen a dispute

Opens a formal dispute against a receipt. Triggers a verifier node to re-execute the request. Disputes must be filed within 24 hours of settlement.

ParameterTypeRequiredDescription
intentHashstringrequiredIntent hash of the disputed settlement.
reasonstringrequiredoutput_mismatch | latency_exceeded | provider_unavailable | other
evidenceobjectoptionalOptional supporting evidence (output diff, timing logs).

Receipts API

GET/v1/receipts/:receiptHashFetch a receipt

GET/v1/receiptsList receipts
ParameterTypeRequiredDescription
agentDidstringoptionalFilter by agent DID.
providerstringoptionalFilter by provider DID.
capabilitystringoptionalFilter by capability.
verificationMethodstringoptionaldeterministic-reexecution | merkle-proof | zk-proof
from / toISO 8601optionalDate range filter.
limit / cursorinteger / stringoptionalPagination.

POST/v1/receipts/:receiptHash/verifyTrigger re-verification

Manually request a verifier node to re-verify an existing receipt. Returns the verification result and the verifier's signature.

Errors & Status Codes

All errors follow a consistent envelope. The code field is stable across versions; the message field is human-readable and may change.

Error responsejson
{
  "error": {
    "code": "INTENT_BUDGET_EXCEEDED",
    "message": "No eligible provider found within the declared maxUsd budget of $0.01.",
    "intentHash": "sha256:3f4a…",
    "docs": "https://docs.novault.xyz/errors/INTENT_BUDGET_EXCEEDED"
  }
}
CodeHTTPDescription
AUTH_INVALID_KEY401API key not found or revoked.
AUTH_KEY_EXPIRED401API key has passed its expiresAt date.
INTENT_INVALID_SCHEMA422Input payload failed schema validation against the resolved provider.
INTENT_BUDGET_EXCEEDED422No eligible provider exists within the budget constraint.
INTENT_TTL_EXPIRED408Execution did not complete within the declared ttlMs.
INTENT_DUPLICATE_NONCE200Nonce was already used within 24 h. Original result returned.
PROVIDER_UNAVAILABLE503Resolved provider returned an error or timed out. Retry with fallback.
SETTLEMENT_ESCROW_FAILED502On-chain escrow transaction reverted. Funds not moved.
RECEIPT_VERIFICATION_FAILED200Receipt signature invalid. Refund initiated; provider dispute opened.
RATE_LIMITED429Request rate exceeds your plan limit. Check Retry-After header.

TypeScript / JavaScript SDK

The official TypeScript SDK is a zero-dependency library that provides a fully typed wrapper over the NoVault REST API, with built-in intent signing, receipt verification, and streaming support.

Installation

npm install @novault/sdk
# peer dep for streaming intents (optional)
npm install eventsource

Client options

ParameterTypeRequiredDescription
apiKeystringrequiredYour NoVault API key.
agentIdentityAgentIdentityoptionaled25519 keypair for intent co-signing. Generate with generateAgentIdentity().
environment"sandbox" | "production"optionalDefaults to production.
timeoutnumberoptionalRequest timeout in milliseconds. Default 30 000.
maxRetriesnumberoptionalClient-level retry count for network errors (not provider failures). Default 2.
fetchfunctionoptionalCustom fetch implementation. Defaults to global fetch.

Streaming intents

For capabilities that support streaming output (e.g. llm/complete), use the streaming API:

const stream = await nv.intent.stream({
  capability: "llm/complete",
  input: { prompt: "Explain non-custodial settlement in one paragraph.", maxTokens: 200 },
  budget: { maxUsd: "0.02" },
});

for await (const chunk of stream) {
  process.stdout.write(chunk.delta);
}

const receipt = await stream.receipt();  // available after stream ends
console.log("Paid:", receipt.amountPaidUsd);

Python SDK

pip install novault

Basic usage

example.pypython
import asyncio
from novault import NoVault, generate_agent_identity

async def main():
    identity = await generate_agent_identity()

    nv = NoVault(
        api_key="nv_live_sk_…",
        agent_identity=identity,
        environment="sandbox",
    )

    result = await nv.intent.execute(
        capability="data/price",
        input={"asset": "BTC", "currency": "USD", "aggregation": "median"},
        budget={"max_usd": "0.001"},
    )

    print(f"BTC price: {result.output['price']} {result.output['currency']}")
    print(f"Receipt:   {result.receipt.hash}")
    print(f"Paid:      {result.receipt.amount_paid_usd} USD")

asyncio.run(main())

Sync client

from novault.sync import NoVaultSync

nv = NoVaultSync(api_key="nv_live_sk_…")
result = nv.intent.execute(capability="data/price", input={…}, budget={…})

REST (cURL)

The REST API is the canonical interface. All SDKs are thin wrappers over it. Use cURL or any HTTP client for languages without an official SDK.

Execute an intent

cURLbash
curl -X POST https://api.novault.xyz/v1/intents \
  -H "Authorization: Bearer nv_live_sk_…" \
  -H "Content-Type: application/json" \
  -d '{
    "capability": "data/price",
    "input": { "asset": "ETH", "currency": "USD", "aggregation": "median" },
    "budget": { "maxUsd": "0.001" },
    "policy": { "ttlMs": 5000, "requireVerification": true }
  }'

Response

{
  "intentHash": "sha256:3f4a…",
  "status": "settled",
  "output": {
    "price": "3421.87",
    "currency": "USD",
    "timestamp": 1753996800
  },
  "receipt": {
    "hash": "sha256:9c1b…",
    "provider": "did:novault:0xabcd…",
    "amountPaidUsd": "0.0004",
    "latencyMs": 312,
    "verificationMethod": "deterministic-reexecution",
    "signature": "ed25519:0x7e3f…"
  }
}

x402 Payment Standard

x402 is an open HTTP payment standard that embeds payment authorization directly in the request/response cycle. NoVault uses x402 for all settlement operations.

The standard defines two new HTTP headers: X-Payment (client → server, carries payment authorization) and X-Payment-Response (server → client, carries settlement receipt).

Flow

1. Server announces payment requirement (402 response)http
HTTP/1.1 402 Payment Required
X-Payment-Required: {
  "version": "1",
  "scheme": "exact",
  "network": "base-mainnet",
  "maxAmountRequired": "40000",
  "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "payTo": "0xEscrowContract…",
  "extra": { "capability": "image/upscale" }
}
2. Client retries with payment headerhttp
POST /execute HTTP/1.1
X-Payment: {
  "version": "1",
  "scheme": "exact",
  "network": "base-mainnet",
  "payload": "0x…",       // signed EIP-712 authorization
  "extra": {
    "intentHash": "sha256:3f4a…",
    "agentSignature": "ed25519:0x…"
  }
}
3. Server confirms settlementhttp
HTTP/1.1 200 OK
X-Payment-Response: {
  "success": true,
  "txHash": "0xchain…",
  "receipt": { … }
}
x402 is an open standard. You can use it independently of NoVault to add per-request payments to any HTTP API. x402.org

Escrow Contracts

NoVault's escrow contracts are deployed on Base mainnet (and Base Sepolia for sandbox). They are immutable, audited, and governed entirely by protocol rules — no operator can drain them unilaterally.

Contract addresses

ContractNetworkAddress
IntentEscrowBase Mainnet0x1a2b3c4d…
ProviderRegistryBase Mainnet0x5e6f7a8b…
SlashingControllerBase Mainnet0x9c0d1e2f…
IntentEscrowBase Sepolia0xaabbccdd…

Security properties

  • Funds cannot be withdrawn until a valid execution receipt is verified or the TTL expires.
  • The agent co-signature is required to open escrow — the API key alone is insufficient.
  • Re-entrancy protection and integer-overflow checks throughout.
  • Audited by Trail of Bits and Spearbit (reports linked in the GitHub repo).

Slashing & Disputes

Slashing is the mechanism that makes provider misbehavior economically costly. Collateral is seized when a provider's execution receipt fails verification.

Slashing schedule

OffenseSlash amountSuspension
Output hash mismatch5× request valueNone (1st offense)
Output hash mismatch (repeated)20× request value24 h suspension
SLA latency exceeded (>2×)1× request valueNone
SLA latency exceeded (>5×)5× request value1 h suspension
Schema mismatch (input rejected)1× request valueNone
Malicious output (manual review)100% collateralPermanent ban

Dispute resolution timeline

  • T+0 — Receipt fails verification. Refund is initiated immediately.
  • T+1 h — Verifier node completes re-execution. Dispute outcome recorded on-chain.
  • T+24 h — Provider may challenge the outcome via governance vote (requires 5% of bonded collateral as challenge stake).
  • T+7 d — Governance challenge period ends. Slashing executed if no successful challenge.

ZK Verification Roadmap

Deterministic re-execution works well for data feeds, transforms, and structured queries. It cannot verify non-deterministic capabilities like LLM inference or generative image models, where two correct executions of the same input may produce different (but equally valid) outputs.

The NoVault receipt format is forward-compatible with ZK attestation. The verificationMethod field is designed to accept a "zk-proof" value, and the receipt schema includes reserved fields for the proof and verification key.

Roadmap phases

Phase 1complete

Deterministic re-execution

Live on mainnet. Covers all deterministic capability classes.

Phase 2in-progress

Merkle-proof verification

Large-output capabilities (compute/render, data/bulk). Q3 2026.

Phase 3planned

ZK proof of LLM inference

Partnered with Boundless (RISC Zero) for SP1 proof generation. Mainnet target Q1 2027.

Phase 4research

ZK proof of generative models

Active research. Requires hardware-accelerated ZK proving for GPU workloads.

Simple Agent Request

A complete example: an autonomous agent that fetches an ETH price, upscales a chart image, and logs both receipts.

agent.tstypescript
import { NoVault, generateAgentIdentity } from "@novault/sdk";

const identity = await generateAgentIdentity();
const nv = new NoVault({ apiKey: process.env.NOVAULT_API_KEY!, agentIdentity: identity });

// ── Step 1: fetch ETH price ────────────────────────────────────────────────
const priceResult = await nv.intent.execute({
  capability: "data/price",
  input: { asset: "ETH", currency: "USD", aggregation: "median" },
  budget: { maxUsd: "0.001" },
});
const ethPrice = priceResult.output.price;
console.log("ETH/USD:", ethPrice);

// ── Step 2: upscale a chart image ─────────────────────────────────────────
const imageResult = await nv.intent.execute({
  capability: "image/upscale",
  input: { url: `https://charts.example.com/eth?price=${ethPrice}`, scale: 4 },
  budget: { maxUsd: "0.01" },
});
console.log("Upscaled chart:", imageResult.output.url);

// ── Step 3: log receipts ──────────────────────────────────────────────────
for (const result of [priceResult, imageResult]) {
  console.log({
    capability: result.receipt.capabilityId,
    provider:   result.receipt.provider,
    paid:       result.receipt.amountPaidUsd,
    latency:    result.receipt.latencyMs + "ms",
  });
}

Multi-step Workflow

Run multiple intents concurrently within a shared session budget, with automatic fallback on provider failure.

workflow.tstypescript
import { NoVault, generateAgentIdentity } from "@novault/sdk";

const nv = new NoVault({ apiKey: process.env.NOVAULT_API_KEY!, agentIdentity: await generateAgentIdentity() });

// Create a session with a $1 total budget
const session = await nv.sessions.create({
  budget: { totalMaxUsd: "1.00" },
  ttlSeconds: 300,
});

const opts = { sessionId: session.id, policy: { retries: 2, ttlMs: 10_000 } };

// ── Concurrent data fetch ─────────────────────────────────────────────────
const [eth, btc, sol] = await Promise.all([
  nv.intent.execute({ capability: "data/price", input: { asset: "ETH", currency: "USD", aggregation: "median" }, budget: { maxUsd: "0.001" }, ...opts }),
  nv.intent.execute({ capability: "data/price", input: { asset: "BTC", currency: "USD", aggregation: "median" }, budget: { maxUsd: "0.001" }, ...opts }),
  nv.intent.execute({ capability: "data/price", input: { asset: "SOL", currency: "USD", aggregation: "median" }, budget: { maxUsd: "0.001" }, ...opts }),
]);

console.log("Prices:", eth.output.price, btc.output.price, sol.output.price);

// ── Sequential analysis (depends on prices) ───────────────────────────────
const risk = await nv.intent.execute({
  capability: "data/risk-score",
  input: { portfolio: { ETH: 0.6, BTC: 0.3, SOL: 0.1 }, prices: { ETH: eth.output.price, BTC: btc.output.price, SOL: sol.output.price } },
  budget: { maxUsd: "0.02" },
  ...opts,
});

console.log("Portfolio risk score:", risk.output.score, "/100");
console.log("Session total spent:", (await nv.sessions.get(session.id)).totalSpentUsd);

Full Provider Setup

End-to-end provider registration: identity, collateral, schema, and a minimal execution server.

provider-setup.tstypescript
import { createProviderIdentity, NoVault } from "@novault/sdk/provider";
import { serve } from "@hono/node-server";
import { Hono } from "hono";

// 1. Create provider identity (run once, store securely)
const identity = await createProviderIdentity({ name: "My Upscale Service" });
console.log("Provider DID:", identity.did);

// 2. Bond collateral ($200 covers up to 200 req/min throughput)
const nv = new NoVault({ apiKey: process.env.NOVAULT_PROVIDER_KEY!, providerIdentity: identity });
await nv.provider.bondCollateral({ amountUsd: "200.00" });

// 3. Publish schema
await nv.provider.publishSchema({
  providerDid: identity.did,
  schema: {
    capability: "image/upscale",
    version: "1.0.0",
    input: { type: "object", properties: { url: { type: "string" }, scale: { type: "integer", enum: [2, 4] } }, required: ["url", "scale"] },
    output: { type: "object", properties: { url: { type: "string" } } },
    pricing: { model: "per-request", unit: "request", rate: "0.005", currency: "USDC" },
    sla: { p99LatencyMs: 5000, uptimePct: 99.0 },
    settlement: { method: "x402", payTo: "0xYourWallet…", network: "base-mainnet" },
  },
});

// 4. Execution server (receives requests from NoVault after escrow opens)
const app = new Hono();
app.post("/execute", async (c) => {
  const { input, intentHash } = await c.req.json();

  // Verify the x402 payment header before doing any work
  const isAuthorized = await nv.provider.verifyPaymentHeader(c.req.header("X-Payment")!, { intentHash });
  if (!isAuthorized) return c.json({ error: "Unauthorized" }, 401);

  // Do the actual work
  const upscaled = await upscaleImage(input.url, input.scale);

  // Return output + issue a signed receipt
  const receipt = await nv.provider.issueReceipt({ intentHash, outputHash: await sha256(upscaled.url) });
  return c.json({ output: upscaled, receipt }, 200, { "X-Payment-Response": JSON.stringify(receipt) });
});

serve({ fetch: app.fetch, port: 3000 });
console.log("Provider server running on :3000");