# VendorStacks for Agents

> VendorStacks — the subprocessor disclosure data API. Canonical: https://vendorstacks.com
/docs/agents/


## Overview

VendorStacks is built to be operated by agents, not just called by code a human wrote. Four properties make that work:

- **Instant keys** — `POST /v1/keys` with an email returns the key in the same response. No verification click, so an agent can self-provision mid-task.
- **Deterministic output** — no LLM in the extraction path. The same disclosure always parses to the same stack, every claim carries a verbatim quote, and the API never invents a vendor. Your agent's stochastic layer stays the only one to audit.
- **402 handoff** — running out of credits returns a machine-readable paywall with the exact shortfall and a `topup_url`. Nothing is charged; the retry is idempotent.
- **Self-documenting responses** — error messages carry their own fix (401 tells you how to get a key; 402 tells you what it costs), so an agent can recover without a docs lookup.

### Discovery surface

| Resource | URL | What it is |
| --- | --- | --- |
| Site llms.txt | [vendorstacks.com/llms.txt](/llms.txt) | Orientation + link map for this site |
| Everything, one file | [vendorstacks.com/llms-full.txt](/llms-full.txt) | All docs, guides, and API reference as one markdown document |
| Markdown mirrors | `/docs.md` · `/docs/api.md` · `/docs/guides.md` · `/docs/guides/{slug}.md` · `/docs/agents.md` | Every docs page as plain markdown |
| OpenAPI 3.1 | [vendorradar-production.up.railway.app
/openapi.json](https://vendorradar-production.up.railway.app
/openapi.json) | Spec for codegen and tool generation |
| Agent guide | [vendorradar-production.up.railway.app
/agents](https://vendorradar-production.up.railway.app
/agents) | The API's own agent integration guide |
| Skill file | [vendorradar-production.up.railway.app
/skill](https://vendorradar-production.up.railway.app
/skill) | Installable SKILL.md (see below) |

## Claude Code / Claude skills

The API serves an installable skill file — a markdown document written for a model, teaching the endpoints, the credit economics, and the cost-discipline rules in one fetch.

**Install (Claude Code)**

```
mkdir -p .claude/skills/vendorstacks
curl -s https://vendorradar-production.up.railway.app
/skill \
  -o .claude/skills/vendorstacks/SKILL.md
```

Claude Code discovers skills in `.claude/skills/` automatically and loads this one when a task involves vendor stacks, subprocessor disclosures, entity due diligence, or prospecting by vendor — the skill's frontmatter description is what triggers it.

- What the skill teaches: endpoint map with the flat 1-credit pricing, self-provisioning via `/v1/keys`, timeout discipline for live scans, `found:false` semantics, and the 402 relay pattern.
- For other skill-compatible harnesses (or plain system prompts), fetch the same file and paste it into context — it is self-contained markdown.

## OpenAI function calling

**Function schema**

```
{
  "type": "function",
  "function": {
    "name": "vendorstacks_check_entity",
    "description": "Look up whether an entity (by domain) publishes a subprocessor/DPA disclosure and which vendors it names, as a structured vendor stack across 13 categories with quoted evidence. Every lookup costs 1 credit flat: indexed answers return <1s, unindexed domains live-scan automatically (15-90s, same price) — use a 120s timeout. fresh=true forces a re-scan of an indexed domain at the same price. found=false means no disclosure was located, NOT that the entity uses no vendors.",
    "parameters": {
      "type": "object",
      "properties": {
        "url": { "type": "string", "description": "Domain, e.g. acme.com" },
        "fresh": { "type": "boolean", "description": "Force live re-scan. Default false." }
      },
      "required": ["url"]
    }
  }
}
```

**Usage (Responses API)**

```
import OpenAI from "openai";
const client = new OpenAI();

const response = await client.responses.create({
  model: "gpt-5",
  input: "What vendors does notify-hq.com disclose?",
  tools: [vendorstacksTool], // the schema above
});

for (const item of response.output) {
  if (item.type === "function_call" && item.name === "vendorstacks_check_entity") {
    const { url, fresh } = JSON.parse(item.arguments);
    const data = await fetch(
      `https://vendorradar-production.up.railway.app
/v1/check?url=${encodeURIComponent(url)}` + (fresh ? "&fresh=true" : ""),
      { headers: { Authorization: `Bearer ${process.env.VENDORSTACKS_KEY}` } },
    ).then(r => r.json());
    // feed `data` back as the function_call_output item
  }
}
```

The same schema works in the Assistants API and Chat Completions `tools` array unchanged.

## Anthropic tool use

**Tool definition (Anthropic format)**

```
{
  "name": "vendorstacks_check_entity",
  "description": "Look up whether an entity (by domain) publishes a subprocessor/DPA disclosure and which vendors it names, as a structured vendor stack across 13 categories with quoted evidence. Every lookup costs 1 credit flat: indexed answers return <1s, unindexed domains live-scan automatically (15-90s, same price) — use a 120s timeout. fresh=true forces a re-scan of an indexed domain at the same price. found=false means no disclosure was located, NOT that the entity uses no vendors.",
  "input_schema": {
    "type": "object",
    "properties": {
      "url": { "type": "string", "description": "Domain, e.g. acme.com" },
      "fresh": { "type": "boolean", "description": "Force live re-scan. Default false." }
    },
    "required": ["url"]
  }
}
```

**Messages API example**

```
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();

const msg = await anthropic.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 1024,
  tools: [vendorstacksTool], // the definition above
  messages: [{ role: "user", content: "Vet notify-hq.com's subprocessors." }],
});

const call = msg.content.find(b => b.type === "tool_use");
if (call) {
  const { url, fresh } = call.input;
  const data = await fetch(/* same fetch as the OpenAI example */);
  // return as { type: "tool_result", tool_use_id: call.id, content: JSON.stringify(data) }
}
```

## MCP

**Status: hosted MCP server — coming.** Not shipped yet; this section describes what is planned, not what exists today.

The hosted server will speak the Model Context Protocol over streamable HTTP and expose the core operations as MCP tools:

- `check_entity` — wraps `GET /v1/check` (1 credit flat, with the timeout guardrails baked into the tool description)
- `prospect` — wraps `GET /v1/prospect` reverse lookup with pagination
- `balance` — wraps `GET /v1/balance` so agents can check spend headroom for free

Until it ships, the practical equivalents are the [skill file](#claude) for Claude-family harnesses and the function-calling definitions above for everything else — they cover the same three operations.

## LangChain / LlamaIndex

**LangChain (python)**

```
import os, requests
from langchain_core.tools import tool

@tool
def vendorstacks_check_entity(url: str, fresh: bool = False) -> dict:
    """Look up whether an entity (by domain) publishes a subprocessor/DPA disclosure and which vendors it names, as a structured vendor stack across 13 categories with quoted evidence. Every lookup costs 1 credit flat: indexed answers return <1s, unindexed domains live-scan automatically (15-90s, same price) — use a 120s timeout. fresh=true forces a re-scan of an indexed domain at the same price. found=false means no disclosure was located, NOT that the entity uses no vendors."""
    r = requests.get(
        "https://vendorradar-production.up.railway.app
/v1/check",
        params={"url": url, **({"fresh": "true"} if fresh else {})},
        headers={"Authorization": f"Bearer {os.environ['VENDORSTACKS_KEY']}"},
        timeout=120 if fresh else 15,
    )
    if r.status_code == 402:
        body = r.json()
        return {"needs_human": True, "topup_url": body["topup_url"],
                "credits_needed": body["credits_needed"]}
    return r.json()
```

**LlamaIndex (python)**

```
import os, requests
from llama_index.core.tools import FunctionTool

def check_entity(url: str, fresh: bool = False) -> dict:
    """Look up whether an entity (by domain) publishes a subprocessor/DPA disclosure and which vendors it names, as a structured vendor stack across 13 categories with quoted evidence. Every lookup costs 1 credit flat: indexed answers return <1s, unindexed domains live-scan automatically (15-90s, same price) — use a 120s timeout. fresh=true forces a re-scan of an indexed domain at the same price. found=false means no disclosure was located, NOT that the entity uses no vendors."""
    r = requests.get(
        "https://vendorradar-production.up.railway.app
/v1/check",
        params={"url": url, **({"fresh": "true"} if fresh else {})},
        headers={"Authorization": f"Bearer {os.environ['VENDORSTACKS_KEY']}"},
        timeout=120 if fresh else 15,
    )
    return r.json()

vendorstacks_tool = FunctionTool.from_defaults(fn=check_entity,
                                              name="vendorstacks_check_entity")
```

## Cost discipline for agents

Agents spend real credits. These rules belong in the tool description or system prompt — the model, not your wrapper code, makes the spending decisions:

- **Flat pricing, variable latency.** Every lookup is 1 credit — scans cost nothing extra. The thing to budget is wall-clock time: unindexed domains take 15–90s. Pass `fresh=true` only when the user explicitly asked for a re-scan (same price, always slow).
- **120-second timeouts.** Any lookup can trigger a live scan; a short timeout burns the wait and re-spends on retry.
- **Bulk lookups cost 1 credit each — budget time, not just credits.** Before looping a large list, quote the credit total (`count × 1`) and warn that unindexed domains add 15–90s each of wall-clock time. On the free plan, live scans are capped at 5/day (429 `daily_scan_limit`); any pack removes the cap.
- **On 402, relay `topup_url` verbatim** with `credits_needed` and stop. Don't retry, don't paraphrase the URL, don't switch keys.
- **Warn below 20 credits.** `GET /v1/balance` is free — check it at the start of long runs and surface a warning before the balance can strand a task mid-list.

Worked examples: [Handle 402s gracefully](/docs/guides/handle-402s) and [the agent guide](/docs/guides/agent-tool).

## Machine payments (Tempo / MPP)

**Status: in development.** Nothing described here is live; no dates are promised. This section exists so agent builders know the direction.

Today, a 402 needs a human with a card. The plan is to remove that hop for autonomous workloads: VendorStacks intends to accept stablecoin micropayments from agents via the Machine Payments Protocol (MPP) on Tempo, a payments-focused L1. The intended flow:

- The 402 response carries machine-readable payment details (amount, asset, destination) alongside the existing `topup_url`.
- The agent settles the micropayment on-chain from its own wallet — no checkout page, no card, no human.
- The agent retries the same request with proof of settlement; the API verifies and serves it.

The existing 402 contract stays unchanged for humans — machine payment details will be additive fields. When this ships it will be announced on the [blog](/blog) and documented here first.