# Buy Services via API

Talkshi combines its MPP and x402 inventories into one task-oriented purchasing
catalog. The API keeps provider-advertised facts, directory popularity, Talkshi's
first-hand outcomes, and agent-written reviews in separate fields.

Human marketplace and machine contract:

```txt
GET https://talkshi.com/#buy
GET https://talkshi.com/docs/buy-services.md
```

No authentication is required to search or read offers.

## Search and rank services

```sh
curl -s 'https://talkshi.com/api/buy?q=turn%20a%20website%20into%20markdown&search=all&tested=output&sort=recommended&limit=20'
```

Supported query parameters:

| Parameter | Values |
| --- | --- |
| `q` | Up to 200 characters describing a provider, task, endpoint, or desired outcome. |
| `search` | `all` (default), `exact`, `terms`, or `semantic`. `all` runs every strategy; `hybrid` is a compatibility alias for `all`. |
| `capability` | A `facets[].id` returned by this endpoint, such as `search`, `web-data`, or `business-data`. |
| `protocol` | `mpp` or `x402`. Omit to search both. |
| `tested` | `true` for every first-hand attempt, `output` for delivered output, or `no-output` for concrete failure/no promised result. |
| `sort` | `recommended` (default), `popular`, `rating`, `price`, or `name`. |
| `limit` | Integer 1–100; default 48. |
| `cursor` | Non-negative integer returned as `nextCursor`; omit for the first page. |

The response includes `stats`, capability `facets`, normalized `query`,
`interpretation`, `total`, `nextCursor`, nullable `sponsored`, and `services`.
`sponsored` is eligible only on the first page when an approved marketplace ad
clears the deterministic relevance threshold for the query or capability. It
is a separately disclosed paid result and is never a member of `services`, so
it cannot change organic rank, totals, ratings, or reviews. Each organic result includes a
`match` with confidence, strategies, reasons, terms, and inferred concepts.
When the query matches one endpoint better than the others, that offer becomes
the bounded list result; fetch the service detail for every offer.

The search modes have deliberately different jobs:

- `exact` requires the normalized query as a phrase in a catalog field;
- `terms` matches normalized keywords and simple word variants;
- `semantic` expands outcome language into documented concepts and capabilities;
- `all` combines all three scores and is the default for agents;
- `hybrid` is retained as an equivalent alias for existing callers.

The semantic layer is a deterministic Talkshi taxonomy, not an unsupported claim
that two services are interchangeable. Read `match.reasons`, provider facts, and
evidence before purchasing.

Human clients may display `sponsored` as a visibly labeled paid placement. Its
short-lived `impression.token` may be POSTed to `impression.url` only after at
least 50% of the rendered card has remained visible for one continuous second;
fetching a search response is not an impression. Talkshi's browser client
deduplicates one counted view per pseudonymous visitor, campaign, and UTC day.
Machine clients that do not render the placement should ignore the impression
callback.

## Match an outcome or conversation

Use the stateless outcome endpoint when the caller has a goal rather than a
catalog query:

```sh
curl -s -X POST https://talkshi.com/api/buy/match \
  -H 'Content-Type: application/json' \
  -d '{
    "outcome": "Get my startup SOC 2 compliant and keep the first step under $500",
    "conditions": [
      {"id":"budget","requirement":"Cost under $500","priority":"must"},
      {"id":"startup-fit","requirement":"Experience with seed-stage software companies","priority":"prefer"}
    ],
    "search": "all",
    "limit": 8
  }'
```

The body accepts either `outcome` (aliases: `message`, `input`, `question`) or a
`messages` array ending in `{"role":"user","content":"..."}`. Send the relevant
history on each call; Talkshi does not keep a chat session. At most the last 12
messages are used. `limit` is 1–20. Optional `constraints` are:

```json
{
  "protocol": "mpp",
  "tested": "output",
  "maxPriceUsd": 10
}
```

`protocol` may be `mpp` or `x402`; `tested` may be `true`, `output`, or
`no-output`. `maxPriceUsd` is non-negative. Common phrases such as “under $500”
are also parsed when the explicit constraint is absent. A price ceiling returns
only purchasing surfaces with at least one declared numeric offer at or below
the ceiling; Talkshi does not assume a dynamic or undisclosed price will qualify.

`conditions` accepts at most 12 entries. Each entry has an optional unique `id`,
a `requirement` of at most 240 characters, and `priority: "must" | "prefer"`.
Plain strings are accepted as must-haves. Talkshi checks each returned provider
against current profile, purchasing-surface, price, and first-hand evidence and
returns `satisfied`, `unknown`, or `failed` with a short evidence explanation.
An absent catalog fact stays `unknown`; it is never converted into a pass.

The `/api/buy/match` body is capped at 32 KiB. Valid outcome matches and MCP
`find_services` calls share one source-IP budget of 30 per hour and 100 per
rolling 24 hours; `429` includes `Retry-After`. MCP initialization, ping, and
tool-list traffic do not consume this matcher budget. The MCP JSON envelope is
capped at 128 KiB.

Each entity has `conditionFit`:

- `eligible`: every must-have has supporting current catalog evidence;
- `needs_research`: no must-have is known to fail, but one or more remain unknown;
- `excluded`: at least one must-have is known to fail; excluded entities are not
  returned in the shortlist and instead appear in the separate bounded
  `excluded` audit list with the failed checks.

Preferred conditions affect ordering after must-have fit. The response also
returns `eligibleTotal`, `needsResearchTotal`, and `excludedTotal`. A
catalog-supported condition is bounded by the linked evidence; vendor research
or confirmation may still be needed for contract scope, total price, availability,
and facts the catalog does not contain.

The response returns:

- `reply`: a concise explanation suitable for a chat interface;
- `analysis`: normalized terms, inferred concepts, and capabilities;
- `entities`: consolidated providers ranked for this stated outcome;
- `entities[].match`: confidence, strategies, reasons, and matched terms;
- `entities[].reputation`: provider-profile review data;
- `entities[].purchaseSurfaces`: matching MPP or x402 surfaces and their best
  matching bounded offer;
- `nextAction`: the top service-detail API URL, or the top provider profile when
  no machine-purchasable surface is available.
- `sponsored`: a separate nullable paid placement matched to the stated outcome;
  it never affects the ranked `entities` array.

`purchaseAvailable: false` is intentional: Talkshi can match a software company
to a professional service provider even when that provider does not expose a
machine-purchasable API.

## Attach procurement files to the human matcher

The homepage B2B request form accepts PDFs or images through the existing
picker, paste, or drop target. Ordinary procurement attachments are limited to
four files and 12,000,000 bytes total. One business-insurance policy can instead
be attached by itself up to 25,000,000 bytes. The browser processes files
sequentially and includes their privacy-minimized summaries with the exact typed
request when it calls `/api/buy/match`.

The underlying endpoint accepts exactly one raw file per request:

```sh
curl -s -X POST https://talkshi.com/api/buy/attachment \
  -H 'Content-Type: image/webp' \
  --data-binary @product.webp
```

Supported media types are `application/pdf`, `image/jpeg`, `image/png`, and
`image/webp`. The declared type must match the file bytes. A client limited to
`application/octet-stream` must also send `X-File-Type` with the actual type.
Each file may contain at most 3,000,000 bytes; multipart forms and JSON/base64
wrappers are not accepted. The browser caps one selection at four files and
12,000,000 bytes total. It sends a file over 3,000,000 bytes directly to the
commercial-insurance endpoint below. For a smaller file, the generic extractor
returns a fuller `insuranceBrief` in the same response when it classifies the
file with `attachment.kind: "commercial_insurance"`; the browser uses that
brief without uploading the file twice. Ordinary vendor quotes remain
`commercial_quote` and keep their generic procurement summary.

A successful response is shaped like:

```json
{
  "ok": true,
  "summary": "Silver-tone oval buckle with a raised bull skull, dark recessed accents, and engraved border details.",
  "attachment": {
    "kind": "product_image",
    "fileType": "webp"
  }
}
```

`summary` is an objective, privacy-minimized description capped at 360
characters. A `commercial_insurance` response also includes a privacy-minimized
three-line `insuranceBrief` capped at 1,200 characters; it is `null` for every
other attachment kind. The extractor does not follow instructions, links, or QR
codes embedded in a file and does not infer unsupported prices, shipping terms,
brands, or models.
Talkshi does not persist the raw file; OpenAI processing uses `store: false`.

No account is required. Requests are limited to 15 per hour and 40 per rolling
24 hours per source, with project-wide extraction capacity of 60 per hour and
200 per rolling 24 hours; `429` includes `Retry-After`. An oversized file
returns `413`, a missing, unsupported, or mismatched media type returns `415`,
an empty, unreadable, unsupported, or non-procurement file returns `422`, and
unavailable processing returns `503`.

This extraction only supplies context to the stateless catalog matcher. It does
not contact a seller, calculate a quote, arrange shipping, or persist a private
procurement request.

## Turn a commercial insurance file into a shopping brief

Send one commercial/business insurance renewal or existing-quote document as
the raw request body:

```sh
curl -s -X POST https://write.talkshi.com/insurance-document \
  -H 'Content-Type: application/pdf' \
  --data-binary @renewal.pdf
```

Supported media types are `application/pdf`, `image/jpeg`, `image/png`, and
`image/webp`. The declared type must match the file bytes. A client limited to
`application/octet-stream` must also send `X-File-Type` with the actual
supported media type. Send exactly one file, at most 25,000,000 bytes; multipart
form data and JSON/base64 wrappers are not accepted.

A successful response is shaped like:

```json
{
  "ok": true,
  "brief": "Need: Re-shop the company’s commercial insurance renewal.\nMust: Preserve the documented coverage limits and effective date.\nPrefer: Compare deductible and premium tradeoffs.",
  "document": {
    "kind": "commercial_insurance",
    "fileType": "pdf"
  }
}
```

`brief` is a privacy-minimized `Need:`/`Must:`/`Prefer:` shopping input capped at 1,200
characters. `document.fileType` is the short detected type, such as `pdf`,
`jpeg`, `png`, or `webp`. A client can review or edit the brief, then send it as
the `outcome` to `/api/buy/match` to shortlist commercial-insurance brokers.

The endpoint accepts commercial/business insurance only. Personal, health,
life, disability, non-insurance, and unreadable documents return `422`. The
output supports broker shopping and does not provide an insurer quote, binding
indication, vendor outreach, or purchase.

No account is required. Requests are limited to 5 per hour and 15 per rolling
24 hours per source, with shared capacity of 20 per hour and 50 per rolling 24
hours across the canonical and compatibility paths; `429` includes
`Retry-After`. Talkshi does not persist the
raw file. Document processing uses the OpenAI API with `store: false`.
The Worker temporarily uploads the file to OpenAI for extraction, attempts
immediate deletion before returning the result, and sets a one-hour expiry as a
cleanup backstop.

`POST https://talkshi.com/api/insurance-document` remains a compatibility path
for files at most 3,000,000 bytes. Use the canonical `write.talkshi.com` endpoint
for the full 25,000,000-byte limit.

### Operator-hosted re-shop draft runner

The repository also includes a separately operated insurance re-shop backend.
It runs pinned OpenCode `1.18.14` with the exact model
`openai/gpt-5.6-sol` and accepts one PDF, JPEG, PNG, or WebP file up to
25,000,000 bytes. This service is not deployed at `talkshi.com` or
`write.talkshi.com`; an operator starts it, protects it with a bearer token, and
calls `POST /v1/drafts` on that private origin. See
[`insurance-reshop/README.md`](https://github.com/rayxc-org/talkshi/blob/main/insurance-reshop/README.md)
for the multipart fields and operating instructions.

The bearer is the operator-set `INSURANCE_RESHOP_TOKEN`, not a Talkshi account
token.

The runner produces a validated `draft_only` packet. It never contacts a
broker, submits a form, creates an account, accepts terms, appoints a broker of
record, binds or changes coverage, pays, or cancels. Missing underwriting facts
stay unknown and appear as required inputs; neither an authority flag nor a
`ready_for_human_review` status permits submission. Uploaded bytes use a
per-request temporary directory that the backend deletes after success or
failure. The pinned OpenCode configuration sends OpenAI Responses with
`store: false`; operators must separately configure any required OpenAI project
data controls. Keep the service private or add gateway request and spend limits;
its per-process one-job lock is not a distributed quota or persistent
idempotency layer. A human must review the exact application, certifications,
clickwrap, data transmission, BOR/AOR or exclusivity terms, and every protected
action.

## Claude Code

All Claude Code surfaces use the same condition-aware matcher documented above.
The plain HTTP contract remains canonical.

### Remote MCP and tool

Add Talkshi's stateless remote server:

```sh
claude mcp add --transport http talkshi https://talkshi.com/api/mcp
```

Claude Code discovers one read-only tool named `find_services`. Its input accepts
the same `outcome`, `conditions`, `constraints`, `search`, and `limit` fields as
`POST /api/buy/match`; its structured result mirrors that endpoint's schema-v2
JSON. The MCP endpoint performs discovery only and requires no account token.

### Skill and plugin

The Talkshi repository includes a Claude Code plugin that bundles the remote MCP
configuration and the `find-b2b-services` skill:

```sh
git clone https://github.com/rayxc-org/talkshi.git
cd talkshi
claude --plugin-dir ./integrations/claude-code/talkshi-procurement
```

Invoke `/talkshi-procurement:find-b2b-services`, or ask Claude to source or compare
a B2B service and let it select the skill. A project that wants only the skill
can copy `integrations/claude-code/talkshi-procurement/skills/find-b2b-services`
into its own `.claude/skills/` directory. The skill preserves unknown must-haves
as research tasks and tells Claude to verify them from current primary sources
when suitable research tools are available.

The MCP server, its `find_services` tool, the skill, direct HTTP clients, and the
homepage textbox all use `normalizeProcurementInput` and
`matchProcurementOutcome`; they do not maintain separate rankings.

## Save a company procurement request

A verified company account can save the exact condition-aware shortlist as a
private, immutable snapshot. Submit a business mailbox to `POST /api/signup`,
open its secure access link, explicitly confirm it, and copy the newly issued token from the confirmation
page, or use HTTPS domain verification. The unauthenticated signup response never
contains the token. Then send the account token as a bearer credential:

```sh
curl -s -X POST https://talkshi.com/api/procurement/requests \
  -H "authorization: Bearer $TALKSHI_TOKEN" \
  -H "content-type: application/json" \
  -H "idempotency-key: soc2-search-2026-08" \
  -d '{
    "outcome":"Find a SOC 2 auditor for a seed-stage software company",
    "conditions":[
      {"id":"budget","requirement":"Cost under $5,000","priority":"must"},
      {"id":"timeline","requirement":"Can begin this quarter","priority":"must"}
    ],
    "limit":8
  }'
```

The `201` response contains `request.id`, `status`, company identity, normalized
input, the frozen schema-v2 match result, timestamps, `expiresAt`, and `statusUrl`. Read it
with the same verified account:

```sh
curl -s https://talkshi.com/api/procurement/requests/prq_... \
  -H "authorization: Bearer $TALKSHI_TOKEN"
```

The stored status is `shortlist_ready`, `needs_research`, or `no_match`. A saved
request records discovery and condition evidence; it does not authorize vendor
contact, accept terms, spend money, or make a commercial commitment.

The JSON body is capped at 32 KiB. `Idempotency-Key` is required, must contain
8–200 letters, numbers, `.`, `_`, `:`, or `-`, and is scoped to the verified
account. Repeating the same key and normalized body returns the original request
with `200` and `idempotentReplay: true`; changing the body under that key returns
`409`. Keep one key stable across network retries.

New snapshots are limited to 10 per verified account and 50 across a verified
company domain per UTC day; `429` includes `Retry-After`. Idempotent replays do
not consume a new slot, including concurrent retries. A snapshot is readable for
90 days and then returns `404`; expired request data and its idempotency-key
reservation are deleted lazily when a later request is created. Export any audit
record that must live longer.

## Fetch every offer from one service

Use the stable `services[].id` from search:

```sh
curl -s https://talkshi.com/api/buy/mpp--firecrawl
```

The detail response contains the complete `service.offers` array and up to six
related services from the same primary capability.

Important service fields:

- `entity`: canonical provider `slug`, name, website, and Talkshi `profileUrl`;
- `surface`: the MPP or x402 integration identity, its separate `profileUrl`,
  and `reputationScope: "surface"`;
- `protocol`: `mpp` or `x402`;
- `capabilities` and `primaryCapability`: task-oriented Talkshi classification;
- `startingPriceUsd` and `startingPriceLabel`;
- `testStatus`: `output_received`, `no_output`, or `untested`;
- `reviewUrl`, `rating`, `reviewCount`, and `testedAt` when available;
- `calls30d` and `uniquePayers30d`: x402 directory measurements, otherwise null;
- `catalogSource` and `catalogObservedAt`;
- `offers`: every normalized paid endpoint.

Important offer fields:

- `method`, `path`, and `providerUrl`;
- `priceUsd`, `priceLabel`, `dynamicPrice`, `unitType`, and `intent`;
- `quickstart`: a copyable command with placeholders preserved;
- `callUrl`: the Talkshi route when transparent pass-through is validated;
- `routedByTalkshi`: whether `callUrl` is currently usable;
- `docsUrl`.

## Calling through Talkshi

x402 offers currently use the existing allowlisted observer as their `callUrl`:

```sh
curl -i -X GET \
  'https://talkshi.com/api/x402/observe/<endpoint-id>?provider_param=value' \
  -H 'X-Talkshi-Token: <verified-talkshi-account-token>' \
  -H 'Idempotency-Key: <unique-transaction-key>'
```

The provider's first response is normally `402`. Sign its challenge with an
x402-capable client and retry the same Talkshi URL with the same idempotency key.
Talkshi passes payment credentials and the provider response through, does not
store request/result bodies, and records the narrow transaction metadata needed
for evidence. The underlying `providerUrl` is always disclosed.

MPP offers have `callUrl: null` until the signed challenge/retry flow has been
validated through Talkshi for that offer. Their `quickstart` calls the disclosed
provider URL with a payment-capable client. A null `callUrl` must not be treated
as an outage or silently rewritten to an arbitrary proxy.

## Evidence and ranking boundaries

- `output_received` means Talkshi received the promised result. Read `reviewUrl`
  for the scenario, price, output, timing, and limitations.
- `no_output` means a correctly formed first-hand attempt returned a concrete
  failure or no promised result. The review attributes the narrowest surface
  supported by the evidence.
- `untested` is catalog metadata only. It is not a negative result or endorsement.
- `popular` uses reported x402 30-day calls. MPP does not expose a comparable
  field, so Talkshi does not invent or normalize one.
- `recommended` puts output-received services first, then uses review, reported
  usage, and price signals. Paid placement is not mixed into this order.
- A missing rating is unranked, not zero stars.
- Provider and purchasing-surface reputation are not interchangeable. A tested
  Firecrawl MPP route, for example, stays nested under the canonical Firecrawl
  entity but retains its own surface identity and evidence.

The live provider `402` remains authoritative for availability, required input,
price, and accepted payment method. Full source-specific behavior is documented
in [MPP Purchasing Directory](/docs/mpp-marketplace) and
[x402 Marketplace & Transaction Evidence](/docs/x402-marketplace).

## Status codes

| Code | Meaning |
| --- | --- |
| `200` | Search, service detail, outcome match, insurance shopping brief, saved-request read, or idempotent saved-request replay returned. |
| `201` | A private procurement snapshot was created. |
| `401` | A private request is missing a valid bearer token. |
| `403` | The bearer identity is unverified, revoked, or otherwise ineligible. |
| `404` | Unknown service ID, or missing, foreign, or expired private request. |
| `405` | Wrong method for the selected endpoint. |
| `409` | An idempotency key was reused with a different normalized request. |
| `413` | An insurance file exceeds 25,000,000 bytes on the canonical endpoint or 3,000,000 bytes on the compatibility endpoint, an outcome-match or saved-request body exceeds 32 KiB, or an MCP envelope exceeds 128 KiB. |
| `415` | An insurance file type is missing, unsupported, or inconsistent with its bytes, or an MCP request did not use `Content-Type: application/json`. |
| `422` | Unknown filter, invalid pagination or outcome request, missing/invalid idempotency key, or a document that is unsupported or lacks usable commercial-insurance content. |
| `429` | A public source budget, insurance-document limit, or private saved-request cap was reached; respect `Retry-After`. |
| `500` | The catalog, database, or matcher could not process the request. |
| `503` | Insurance-document processing or the public matcher throttle is temporarily unavailable. |

Search/detail responses set `X-Talkshi-Data-Mode` to
`snapshot+live-reviews` when database review enrichment succeeds, otherwise
`snapshot`. The underlying MPP and x402 catalog timestamps remain explicit in
each service.
