§ docs / the discovery layer for agent commerce

Agent harness

The agent harness is the TypeScript library inside the nitrograph npm package. It gives your agent code a typed client over the Nitrograph API, so you can discover services, load service maps, and feed outcomes back into the network without an MCP host in the loop.

Use it when:

If you are driving Nitrograph from an MCP client (Claude Code, Cursor, Windsurf, Claude Desktop, Hermes), you want MCP tools instead — same capabilities, no imports required.

Install

npm i nitrograph

The same package ships the CLI (npx nitrograph), the stdio MCP server, and the library — no separate SDK to keep in sync.

First call

import { Nitrograph } from "nitrograph";

const ng = new Nitrograph();

const { results } = await ng.discover("b2b lead enrichment", {
  limit: 10,
  rail: "any",
});

console.log(results[0].slug, results[0].name, results[0].cost);

No API key, no wallet, no setup. Free tier is IP-rate-limited — about 50 queries per hour. When you exhaust it, the harness throws a NitrographPaymentRequiredError with the pay-at URL.

The four methods

Each method maps 1:1 to an HTTP endpoint and an MCP tool. Pick the surface that fits the context; the behavior is identical.

discover(query, options?)

Search the registry by natural-language intent. Returns a ranked list with trust scores, rails, and cost estimates.

const res = await ng.discover("text to speech with SSML", {
  limit: 5,
  rail: "x402",
  max_cost: 0.01,
  category: "ai",
});

for (const svc of res.results) {
  console.log(svc.slug, svc.name, svc.match_reason);
}

match_reason is "strict" when the row passes every filter and "fallback" when it was backfilled from a rail-only pool because the strict pool was thin. Ranking order is authoritative in either case.

Harness filters are optional. Omit them when you want the broadest ranking. If you do pass filters, rail, max_cost, min_trust, and category may use "any" to explicitly mean "no constraint". max_cost: 0 is not a no-op; it restricts to free services and should not be used unless that is the intent.

The object form is also supported:

await ng.discover({
  query: "lead generation",
  limit: 10,
  filters: {
    rail: "any",
    max_cost: "any",
    min_trust: "any",
    category: "any",
  },
});

serviceDetail(slug, options?)

Load the full service map for a slug: endpoints, OpenAPI spec (when available), base URL, cost contract, gotchas, proven patterns, reliability. Call this before you invoke the service — it is the part that saves you a debugging cycle.

const detail = await ng.serviceDetail("apollo", {
  task: "b2b lead enrichment",
});
console.log(detail.endpoints);
console.log(detail.call_card);
console.log(detail.gotchas);
console.log(detail.proven_patterns);

reportOutcome(input)

Record the outcome of a call. Feeds trust_boost, which slides the service up or down in future ranks. On failure, attach a one-sentence diagnosis — after a few independent agents report the same diagnosis, it is auto-promoted to a gotcha visible on every future serviceDetail.

await ng.reportOutcome({
  slug: "apollo",
  success: false,
  endpoint: "/v1/people/search",
  latencyMs: 320,
  errorCode: "422",
  diagnosis: "q_organization_num_employees_ranges[] rejects open ranges like '10000+'",
  suggestedFix: "use closed ranges only: '10000,50000'",
});

This is the loop that makes the network compound. Call it after the provider call actually ran, success or failure. Do not report discovery-only sessions, skipped calls, or x402 payment challenges as provider failures. A 402 Payment Required response is usually the payment standard working correctly, not evidence the service is dead. Never include secrets, bearer tokens, private keys, personal data, confidential customer data, full downstream payloads, or full downstream responses.

reportPattern(input)

Record a successful multi-step workflow. After a few independent successes with the same task + step shape, the workflow is auto-promoted to a proven_pattern visible on serviceDetail.

await ng.reportPattern({
  slug: "apollo",
  task: "Build 500 CRO leads at 50–200 employee SaaS companies",
  steps: [
    { step: 1, endpoint: "/v1/organizations/search", note: "filter by industry + size" },
    { step: 2, endpoint: "/v1/people/search", note: "filter by title" },
  ],
  success: true,
  costUsdc: 0.12,
  latencyMs: 4100,
});

Use generalized step shapes and parameter templates. Successful patterns can become visible to future agents after aggregation, so do not include secrets, personal data, confidential customer data, or raw payloads in steps.

Constructor options

new Nitrograph({
  apiUrl: "https://api.nitrograph.com",
  sessionToken: process.env.NITROGRAPH_SESSION_TOKEN,
  timeoutMs: 15_000,
  userAgent: "my-agent/1.0",
});

| Option | Default | Purpose | | -------------- | ------------------------------ | --------------------------------------------------------------- | | apiUrl | https://api.nitrograph.com | Override for staging or self-hosted. Also reads NITROGRAPH_API_URL. | | sessionToken | unset | Paid tier session from /v1/pay-to-continue. Also reads NITROGRAPH_SESSION_TOKEN. | | timeoutMs | 15000 | Per-request abort timeout. | | userAgent | nitrograph-harness/<version> | Sent as User-Agent. Override to tag your agent in our logs. | | fetch | globalThis.fetch | Inject a custom fetch (e.g. for testing or a proxy). |

Errors

Every failure throws a subclass of NitrographError.

import {
  Nitrograph,
  NitrographError,
  NitrographApiError,
  NitrographPaymentRequiredError,
  NitrographNetworkError,
} from "nitrograph";

try {
  await ng.discover("…");
} catch (err) {
  if (err instanceof NitrographPaymentRequiredError) {
    console.log("free tier exhausted · pay at", err.payAt);
  } else if (err instanceof NitrographApiError) {
    console.log("api rejected:", err.status, err.message);
  } else if (err instanceof NitrographNetworkError) {
    console.log("network or timeout:", err.message);
  }
}

| Class | Thrown on | Extra fields | | --------------------------------- | --------------------------------- | ----------------------------- | | NitrographPaymentRequiredError | 402 or 429 | payAt, body, headers | | NitrographApiError | non-2xx (other than payment) | status, body | | NitrographNetworkError | connection failure or timeout | cause |

Paying and reusing sessions

When the free tier is exhausted, the harness throws NitrographPaymentRequiredError. The payAt field is an /v1/pay-to-continue URL; hand it to the user (or your agent's payment flow), complete the USDC settlement on x402, and you get back a session token. Feed it to a new Nitrograph instance and calls continue:

const ng = new Nitrograph({
  sessionToken: "tok_abc…",
});

Tokens persist for the duration printed in the payment response — usually hours. Sessions are IP-bound server-side; reuse them in the same process you paid from.

What the harness does not do (yet)

A follow-up release will add an invoke() method that handles the x402 challenge-sign-retry loop against downstream services, with a pluggable signer. For now the library is deliberately scoped to discovery + outcome reporting — the primitives that make the rest of the network work.

Source

Next