Add a vendor
Write an adapter for an API gtme doesn't ship as one YAML file, verify it offline against a recorded response, and simulate a pipeline through it
What you'll learn
You use a vendor gtme doesn't ship and want to see what an adapter is before handing the job to Claude Code.
- search the registry before writing anything
- write a binding from an API doc, record a fixture, and verify it offline
- where an API key goes
This needs gtme, jq, and one network request to a free public API. No keys, no accounts. If you don't have gtme yet, install it first.
It spends $0 and sends nothing. If gtme already ships your vendors, go to Your stack instead.
Most people have Claude Code write the adapter. This page shows what it writes so you can read it and check it, and the last section gives you the line to hand over.
For a vendor API over plain HTTP, an adapter is a binding: one YAML file the runner interprets, with no code in it. This page uses JSONPlaceholder, a free fake API, so every step runs as written.
Write and verify a binding
-
Search the registry, the gtme-bindings repository on GitHub where people submit the bindings they've written:
gtme adapters search jsonplaceholderno registry entries match "jsonplaceholder" (index: https://raw.githubusercontent.com/gtme-run/gtme-bindings/main/index.json) an agent that finds nothing writes a binding — `gtme help --bindings`A match prints its install command.
gtme adapters addverifies a binding before installing, then pins it: it records the exact version, so an update is a choice you make (ADR-042). -
Save the contract, one JSON document:
gtme help --bindings > bindings.jsonIts
schemakey lists every key a binding may use, andreferenceis a complete binding gtme ships, ready to copy. -
Make the adapter's folder. Its name is the adapter's id with the slash as a dash:
mkdir -p ~/.gtme/adapters/jsonplaceholder-users/fixtures cd ~/.gtme/adapters/jsonplaceholder-usersThis is your real adapters folder, so every pipeline on this machine can use the adapter. To remove it later, run
rm -r ~/.gtme/adapters/jsonplaceholder-users. -
Write
binding.yaml:# jsonplaceholder/users: people from JSONPlaceholder's free fake API. # No auth, no cost. Docs: https://jsonplaceholder.typicode.com # Lines marked "yours" change for your vendor. The rest you copy. id: jsonplaceholder/users # yours: vendor/operation version: 1 role: source # yours: source, enrich, or deliver entity_type: person # yours: person or company # provides: the fields this adapter hands to later steps. Yours. provides: type: object additionalProperties: false properties: full_name: { type: string } email: { type: string } company_name: { type: string } company_domain: { type: string } jsonplaceholder.username: { type: string } # config_schema: the settings a pipeline can pass under with:. config_schema: type: object properties: base_url: { type: string, default: "https://jsonplaceholder.typicode.com" } # yours: the vendor's host request: method: GET # yours url: "{{config.base_url}}/users" # yours: the endpoint's path # extract: where each field sits in the response. Yours. extract: records: "." # the list of records; "." means the whole response fields: full_name: name email: { path: email, transform: email } # transform: a cleanup rule from SPEC §4a company_name: company.name company_domain: { path: website, transform: domain } jsonplaceholder.username: usernameextractmaps each response field to a canonical field. Fields with no canonical name get the vendor's prefix. The validtransformrules are the normalization rules in SPEC §4a, and Binding manifest lists them with every other key. -
Record one real response as the fixture:
curl -fsS https://jsonplaceholder.typicode.com/users \ | jq '{responses: [{match: "GET /users", status: 200, body: .}]}' \ > fixtures/conformance.jsonAgainst a paid vendor, this
curlis one real call, so it costs whatever one call costs. After that, verify and simulate never call the vendor again. -
Verify the binding. Verify prints what it would call and what it would ask for:
gtme adapters verify jsonplaceholder/usersjsonplaceholder/users v1 — source (person) calls: jsonplaceholder.typicode.com demands: none needs: none provides: company_domain, company_name, email, full_name, jsonplaceholder.username (* required) fixtures: ok — 1 response(s) on file, 10 record(s) extractedVerify checks the schema and runs the fixture offline. For a vendor with a key, the binding names it under
credentials, verify prints it underdemands, andgtme secret setstores it. The key itself never goes in the YAML (SPEC §6).
Run a pipeline through it
-
In any folder, save this as
my-pipeline.yaml:name: my-pipeline version: 1 source: use: jsonplaceholder/users with: limit: 3 steps: - id: out use: csv/deliver with: path: out.csv variables: company: company_name idempotency: emailPipeline my-pipeline: source jsonplaceholder/users, then delivers with out (csv/deliver), once per email. Every step writes to the ledger, and cached, AI, and deliver steps read it first. limitisn't in the binding: gtme accepts it on every source and stops after that many records (ADR-047).idempotency: emailis the idempotency rule: one delivery per email to this target, so a rerun never delivers anyone twice. -
Run it under simulate, which answers from your fixture:
gtme run my-pipeline.yaml --simulateThe receipt at the end is similar to the following:
simulate: recorded responses only — no network, no spend, nothing sends, nothing persists run 01M3CX29SWR8TS6ZEW6XEXWJBP (my-pipeline) source [info]: jsonplaceholder/users: 3 records source: sourced 3 records out: 3 in, 0 out, 0 cached, 0 filtered, 0 failed, 3 held (dry run) run 01M3CX29SWR8TS6ZEW6XEXWJBP — done (SIMULATED — recorded responses only; nothing sent, nothing persisted) step adapter in out empty cached filtered failed cost avoided source jsonplaceholder/users 0 3 - 0 - - $0 - out csv/deliver 3 0 - 0 - - $0 - out: resolved variables for 3 record(s) — review, then run again without --dry-run to arm: sincere@april.biz company: "Romaguera-Crona" ... total: $0 spent
That's a new vendor in gtme. The source row shows 3 records from jsonplaceholder/users, served from your fixture, and company came through your extract mapping. held (dry run) and "run again without --dry-run to arm" are the CLI's words under simulate too. Arming means running with no flag at all.
A real vendor adds auth, pagination, and cost to the same file, and Add a vendor with a binding walks through one. An integration that needs conditionals, several calls per record, or an OAuth flow becomes a process adapter, a small program in any language (SPEC §10a).
Hand it to Claude Code
Paste this line into Claude Code for your own vendor:
Follow gtme.run/start.md, "Add a vendor": add VENDOR using its API docs at DOCS_URL.
Replace the following:
VENDOR: the vendor's nameDOCS_URL: the page that documents the endpoint you want
The agent writes the same folder and stops at a verified binding and a simulated receipt for you to read. With the gtme plugin installed, /gtme:create-adapter runs the same steps.
Next
For agents has the plugin install and the rules that keep an agent from spending or sending until you say so.