vendor-listing · git:20260812.f970034 · 2026-08-12 · sha256 05b136d4abe5176f
vendor-listing git:20260812.f970034A
Immutable. This exact content is served forever at /api/v1/blob/05b136d4abe5176f.
---
name: vendor-listing
description: >
Onboard a vendor who wants their API listed in the treg catalog. Use whenever someone asks
"how do we get listed on treg", a vendor sends their API details, or a listing PR/issue needs
review. Walks the whole pipeline: eligibility gate → registry entry → logo → tests → LIVE
bogus-key test → core catalog YAML → verify → scrub → validate. The vendor-facing doc this
skill implements is docs/VENDORS.md.
---
# Vendor listing — add a provider to the catalog
A listing has **two halves**, and both must ship:
1. **Registry entry** (`src/treg/oauth_providers.py`) — how a team connects a credential for the
provider, and how treg verifies that credential is real.
2. **Core catalog file** (`src/treg/catalog/<service>.yaml`) — what an agent can *do*: 8–15
curated endpoints with capability mapping, inputs, cost + provenance, and verified examples.
Deep references (read before non-trivial work; do not duplicate them here):
- `docs/context/guides/expanding-a-category.md` — the add-a-provider playbook, verify toolbox, traps
- `docs/context/architecture/catalog.md` — catalog schema, cost provenance, verify pipeline, PII rules
- `docs/VENDORS.md` — what we told the vendor to prepare (their checklist)
- `src/treg/web/vendor-listing.md` — the HOSTED instructions (served at `/vendor-listing`) that a
vendor's own coding agent follows to raise a listing PR; the dashboard's "List as vendor" modal
(connections view, `vendorAsk` in `index.html`) hands vendors a prompt pointing at it. Keep the
three vendor-facing surfaces (doc, hosted page, modal prompt) telling one story.
## Step 0 — intake: collect the vendor facts
Before touching code, you need ALL of these. If the vendor's submission is missing any, ask —
do not guess ("unconfirmed" beats a wrong path shipped):
- **A contact email for the vendor's team** — required in the PR/issue description. It is how a
test credential gets arranged for live verification; without it the listing stalls at step 7.
- `service` id (lowercase slug), display name, one-line summary (what an agent can DO)
- `base_url` (exact API root)
- Auth: where the key rides (header name + format, or query param name). Key **in the URL path is
not supported** — decline or defer.
- A **free or near-free probe endpoint** where a valid key returns 2xx and an invalid key does NOT
— plus the exact bad-key behavior (status code, or the JSON field that signals invalid)
- Pricing page URL, per-endpoint prices, and the billing model (`per_call` / `per_success` /
`per_result` / credits / quota). Machine-readable rate-card endpoint if they have one.
- Docs URL; OpenAPI spec URL if published
- The 8–15 endpoints they consider their core surface, with example parameter *values*
- A test credential (or credits grant) for verification — read it from env only, never write it
into any file
## Step 1 — eligibility gate
Reject decisively, with a recorded reason, when:
- The key **cannot be validated** (API returns success for garbage keys) — e.g. ScrapeCreators
- Key rides in the **URL path** (`/v3/{key}/…`) — injectors do header/query only
- **Sales-gated** signup (no self-serve key breaks the fast path)
- Legal/shutdown risk, or deprecated/absorbed products
## Step 2 — registry entry
Add an `OAuthProvider(auth_kind="key", …)` in `oauth_providers.py` and append it to `REGISTRY`.
Model it on `HUNTER` (a clean key provider). Pick the verify fields from the toolbox table in
`expanding-a-category.md` (`token_header`/`token_format`, `token_location="query"`+`token_param`,
`probe_url`, `probe_method`+`probe_json`, `token_verify_field`, `token_ok_field`+`token_ok_value`,
`token_reject_field`, `probe_reject_statuses`, …). Prefer a header over a query key so the secret
never lands in a logged URL. Set `category` (add to `CATEGORY_ORDER` only if genuinely new),
`summary`, `base_url`, `docs_url`, `probe_path`, and `setup_url`/`setup_steps` so a user can find
their key.
## Step 3 — logo
`src/treg/web/logos/<service>.svg` — a **neutral lettermark**, not the real brand mark.
`test_every_provider_has_a_logo` fails without it.
## Step 4 — tests
- Add the id to `test_every_provider_is_registered` (test_oauth_providers_m3)
- Add it to the offerable loop in `test_key_providers`
## Step 5 — LIVE bogus-key test (load-bearing; never skip)
Start the server, `POST /connections/token` with a **garbage key** against the real API:
- `422 "rejected …"` → correct. Ship it.
- `200` → the probe does not validate the key → fix the verify fields or drop the provider.
- `404`/`502` in the reason → wrong probe path/host → fix `base_url`/`probe_path`.
**Never ship a key provider you haven't watched reject a bogus key.** Use a throwaway org
(`e2e-…@treg.local`) and delete it after. Watch for the known traps: trailing-slash 307 (put the
slash in `probe_path`), 200-with-error-body (read a body field), CSV/text responses.
## Step 6 — core catalog YAML
`src/treg/catalog/<service>.yaml`, following the schema in `catalog.md`. In order:
1. **Ingest** from OpenAPI if published (never hand-transcribe paths); else from docs with
`source.openapi: null`.
2. **Select** ~8–15 endpoints; ALWAYS include ones matching capabilities other providers already
implement (overlap enables comparison).
3. **Map** each to a capability from `capabilities.yaml`; missing jobs go under
`proposed_capabilities:` in the provider file, not straight into the shared taxonomy.
4. **Describe** `input` (param names, types, required, location; constraints into `note`).
5. **Cost** with full provenance: `type/value/currency/per/unit` + `source/source_url/checked/
confidence`. Unknown price → `value: null` + `confidence: unknown` + a note. Prefer a
rate-card endpoint (`source: rate_card_api`) over a pricing page.
6. **test_request** per endpoint — CHEAP: smallest limit, one item, public well-known target.
⚠️ Never probe with empty params "expecting a validation error": a no-required-params endpoint
returns its full default result set and bills for it (the Moz quota trap).
## Step 7 — verify, scrub, validate
```bash
TREG_CATALOG_CRED='<secret>' uv run --frozen python scripts/catalog_verify.py <service>.yaml
uv run --frozen python scripts/catalog_validate.py # must exit 0
uv run --frozen python -m pytest -q
```
- Stamp `verified:` only on endpoints that PASSED. Docs lie; documented ≠ verified.
- **Scrub every captured example** (this repo is public): no named private individuals
(contact-lookup routes get `untestable:` + no test_request + no example), no third-party
emails/phones riding along, no first-party account identity.
- No credential value anywhere in the diff.
## Step 8 — optional extended tier
If the vendor publishes a stable OpenAPI spec with example parameter values, add an
`ingest_<service>()` to `scripts/catalog_ingest.py`, register it in `INGESTERS`, and generate
`<service>.extended.yaml`. Rules: never probe with a real call; platform = what the data is
ABOUT; normalise platform slugs across providers. Bulk-verify with
`catalog_verify_extended.py --dry-run` first, then with an explicit `--budget`.
## Step 9 — done means
- Validator exits 0; suite green; bogus-key rejection observed live
- The PR/issue carries the vendor's contact email (and no credential value anywhere)
- Every endpoint carries `verified:` + example, or an explicit reason it couldn't be live-tested
- Docs synced: run `bash .claude/skills/tools-registry-context/scripts/drift.sh`, update touched
fragments in the same commit
- If pricing has `confidence: verified|documented` with a computable USD figure and the endpoints
aren't `own_account`/`kind: account`, the vendor is **platform-eligible** — tell them treg can
additionally serve their endpoints on its own key once the provider is keyed and allow-listed
(`platform_key_for`), which is a separate ops decision, not automatic.