# VendorStacks Documentation

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


## Introduction

VendorStacks is a data API over public subprocessor and DPA disclosures. Given an entity (a domain) it answers: does this entity publish a disclosure, where does it live, and which vendors are on it — returned as a structured vendor stack across 13 categories with the quoted disclosure row as evidence. Reverse lookup returns the entities that list a given vendor or category. It is built for GTM enrichment pipelines and AI agents; extraction is deterministic — no LLM, no invented vendors.

Base URL: `https://vendorradar-production.up.railway.app
`. All responses are JSON and carry an `X-Request-Id` header.

### Quick start

**1 · Get a key (instant, 25 free credits)**

```
curl -X POST https://vendorradar-production.up.railway.app
/v1/keys \
  -H "Content-Type: application/json" \
  -d '{"email": "you@company.com"}'
```

**2 · Check an entity**

```
curl "https://vendorradar-production.up.railway.app
/v1/check?url=stripe.com" \
  -H "Authorization: Bearer vr_live_..."
```

**3 · Reverse lookup by vendor**

```
curl "https://vendorradar-production.up.railway.app
/v1/prospect?vendor=twilio&category=sms_messaging" \
  -H "Authorization: Bearer vr_live_..."
```

### Agent-first surface

- [vendorradar-production.up.railway.app
/llms.txt](https://vendorradar-production.up.railway.app
/llms.txt) — orientation file for LLMs
- [vendorradar-production.up.railway.app
/openapi.json](https://vendorradar-production.up.railway.app
/openapi.json) — OpenAPI 3.1 spec for codegen and tool generation
- [vendorradar-production.up.railway.app
/skill](https://vendorradar-production.up.railway.app
/skill) — installable SKILL.md for Claude Code and friends
- [vendorradar-production.up.railway.app
/agents](https://vendorradar-production.up.railway.app
/agents) — agent integration guide
- [/llms-full.txt](/llms-full.txt) — every page of these docs as one markdown document

## Authentication

Authenticated endpoints take a Bearer key: `Authorization: Bearer vr_live_…`. Keys are issued instantly — POST an email to [/v1/keys](/docs/api#keys) and the key arrives in the same response. No verification click, which means an agent can self-provision mid-task.

The plaintext key is shown exactly once and never stored — only a SHA-256 hash is kept server-side, so a lost key cannot be recovered, only replaced. Rotate keys from the [dashboard](/app/keys): create a new key, move traffic, revoke the old one. Revocation is immediate.

## Credits & pricing

Everything is prepaid credits, and every lookup costs the same: **1 credit flat**. If the entity is already in the index you get the answer in under a second; if it isn't, the API live-scans it automatically (15–90s) at no extra cost. Credits never expire.

| Operation | Endpoint | Cost | Latency |
| --- | --- | --- | --- |
| Lookup | `GET /v1/check` (live-scans automatically when unindexed) · `GET /v1/company/{domain}` (never scans) | 1 credit | <1s indexed · 15–90s live scan |
| Reverse-lookup page | `GET /v1/prospect` (10 entities) | 1 credit | <1s |

Packs: $10 → 1,100 credits · $50 → 6,000 · $250 → 35,000. New keys start with 25 free credits; the free plan allows 5 live scans per day (indexed lookups are uncapped) — any pack removes the cap. Machine-readable pricing is served at [/v1/pricing](/docs/api#pricing).

When a request would cost more than the key holds, the API answers 402 Payment Required with the exact shortfall and a `topup_url` — nothing is charged, and the retry after top-up is idempotent. See [Errors](#errors) for the full anatomy.

## Usage limits

| Limit | Value | Behavior |
| --- | --- | --- |
| Request rate | 60 requests / minute / key | 429 with a `Retry-After` header (seconds). Never charged. |
| Free-plan live scans | 5 per day | Indexed lookups are uncapped. Exceeding the allowance returns 429 `daily_scan_limit`; any credit pack removes the cap. |
| Concurrent live scans | 3 per key, platform-wide | Additional scans queue. Concurrent requests for the same domain share one scan (and one charge) — per-domain dedup. |
| Key issuance | 3 keys / IP / day on `/v1/keys` | 429 beyond that; existing keys are unaffected. |
| Request body | 32KB max | 413 `payload_too_large`. |

- Set client timeouts to 120s — an unindexed domain triggers an automatic live scan that legitimately takes 15–90 seconds, at the same 1-credit price.
- The index already covers most entities you'll ask about, so most lookups return in under a second — latency, not price, is the only difference when a live scan kicks in.
- Need more than 60 rpm? `rpm_limit` is configurable per key — [contact us](/enterprise) and we'll raise it.

## Errors

Conventional HTTP status codes with a JSON body. No error response ever charges credits. Every response — success or error — carries an `X-Request-Id` header for correlation; quote it in support requests.

### 400 validation_error

```
{ "error": "validation_error",
  "message": "url must be a domain, e.g. acme.com" }
```

Malformed domain, unknown category slug, bad JSON, or missing required parameter.

### 401 unauthorized

```
{ "error": "unauthorized",
  "message": "Missing or invalid API key. Get one instantly: POST https://vendorradar-production.up.railway.app
/v1/keys {\"email\": ...}" }
```

Missing, malformed, or revoked key. The message self-documents key issuance, so an agent hitting 401 can self-provision without reading docs.

### 402 payment_required

```
{ "error": "payment_required",
  "message": "This request costs 1 credit; balance is 0.",
  "remaining_credits": 0,
  "credits_needed": 1,
  "topup_url": "https://vendorstacks.com/app/billing" }
```

Nothing was charged. Agents: surface `topup_url` and the exact shortfall to a human, pause, and retry after top-up — the retry is idempotent. See the [402 guide](/docs/guides/handle-402s) for the full pattern.

### 404 not_found

```
{ "error": "not_found",
  "message": "No cached profile for that domain." }
```

Unknown route, or a `/v1/company` domain that has never been scanned.

### 413 payload_too_large

```
{ "error": "payload_too_large", "message": "Body exceeds 32KB." }
```

### 429 rate_limited / daily_scan_limit

```
{ "error": "rate_limited",
  "message": "60 requests/minute exceeded.",
  "retry_after_seconds": 12 }
```

```
{ "error": "daily_scan_limit",
  "message": "Free plan allows 5 live scans per day. Indexed lookups remain available; any credit pack removes the cap.",
  "retry_after_seconds": 41230 }
```

`rate_limited` is the per-minute request cap (also sent as a `Retry-After` header) — back off and retry. `daily_scan_limit` is the free plan's live-scan allowance (5/day); indexed lookups keep working, and any pack removes the cap. Neither is ever charged.

### 500 internal_error

```
{ "error": "internal_error",
  "message": "Something broke on our side.",
  "request_id": "9f6f4a1e-…" }
```

Include the `request_id` (also in the `X-Request-Id` header) when contacting support — it pins the exact log line.

### 502 scan_failed

```
{ "error": "scan_failed",
  "message": "Could not reach the target's disclosure pages.",
  "charged": 0 }
```

Fairness rule: a lookup whose live scan fails — unreachable target, no disclosure located — always charges 0 credits.