---
name: kachy
version: 0.1.0
description: Buyer-mandate search agent for fragmented resale supply. Give it a product, size, and budget; it searches live and returns the best executable offer it finds. Fulfillment and payment protection are completed through the Kachy web app using the route shown on each offer.
homepage: https://kachy.app
user-invocable: true
metadata: {"category":"commerce"}
---

```
KACHY API QUICK REFERENCE
Base:   https://api.kachy.app
Auth:   none required (public search endpoint)
Docs:   this file is canonical

Capabilities: search StockX, Bunjang, and Grailed for a live offer against
  a product + size + budget, then accept or reject what it finds

Search:
  POST /intents                  -> start a search
  GET  /intents/:id              -> read status, steps, and offers so far
  POST /intents/:id/decision     -> accept or reject the best offer found
  GET  /intents/:id/stream       -> the same, streamed live over SSE

Rules: no API key, nothing persists after ~30 min post-completion (2h hard
ceiling). Treat every search id as ephemeral. JSON requests.
Errors: HTTP status + { error, message? }
```

# Kachy Search API — Agent Skills Guide

This skill is doc-only. It documents the live Kachy search API below.

## What you can do with Kachy

1. **Search a product across resell marketplaces.** Give it a product, a size, and a budget cap; Kachy checks StockX, Bunjang, and Grailed and surfaces whichever real listing is cheapest.
2. **Watch it work.** Stream the search over SSE and see each platform get checked as it happens, not just the final answer.
3. **Accept or reject the match.** A simple decision endpoint records whether the offer is good enough.
4. **Hand off to fulfill it.** This endpoint never moves money. The Kachy web app shows who fulfills the selected offer and which payment protection applies. Some routes may use USDC escrow; others use a supply partner's checkout and protection policy. See "Completing a purchase" below.

## Base URL

- Base URL: `https://api.kachy.app`
- No authentication. This is a public, unauthenticated search endpoint, so don't rely on a search id staying private.
- Content-Type: `application/json`

## Tool Call Pattern

**Headers (include on every request)**
```bash
-H "Content-Type: application/json" \
-H "Accept: application/json"
```

### API Endpoints Reference

| Endpoint | Method | Path | Params/Body |
|----------|--------|------|-------------|
| Start a search | POST | `/intents` | Body: `productQuery` (string, 1-200 chars), `productSize` (string, 1-20 chars), `maxPriceUsd` (number, up to 50000), optional `platforms` (array of `stockx`/`bunjang`/`grailed`, defaults to all three) |
| Get one search | GET | `/intents/{id}` | Returns status, the trace of steps taken, and any offers found so far |
| Accept or reject | POST | `/intents/{id}/decision` | Body: `decision` (`accept` or `reject`). Only valid once status is `awaiting_confirmation` |
| Live progress | GET | `/intents/{id}/stream` | Server-sent events: `offers`, `best`, `step`, `status`, and a final `complete` |

### POST `/intents` — Start a search

```bash
curl -sS -X POST "https://api.kachy.app/intents" \
  -H "Content-Type: application/json" \
  -d '{
    "productQuery": "Air Jordan 1 Bred Toe",
    "productSize": "10",
    "maxPriceUsd": 280
  }'
```

Response (201):
```json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "running"
}
```

The search kicks off before the response is sent, so `status` is usually already `running` by the time you see it, not `pending`.

### GET `/intents/{id}` — Read a search

```bash
curl -sS "https://api.kachy.app/intents/3fa85f64-5717-4562-b3fc-2c963f66afa6"
```

Response:
```json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "awaiting_confirmation",
  "input": {
    "productQuery": "Air Jordan 1 Bred Toe",
    "productSize": "10",
    "maxPriceUsd": 280,
    "platforms": ["stockx", "bunjang", "grailed"]
  },
  "steps": [
    { "idx": 0, "ts": 1753234000000, "action": "scouting stockx", "platform": "stockx" }
  ],
  "offers": {
    "stockx": {
      "status": "done",
      "platform": "stockx",
      "productFound": true,
      "productName": "Air Jordan 1 Retro High OG Bred Toe",
      "productUrl": "https://stockx.com/...",
      "imageUrl": "https://images.stockx.com/...",
      "lowestAskUsd": 235,
      "condition": "Deadstock",
      "available": true
    },
    "bunjang": { "status": "pending" },
    "grailed": { "status": "pending" }
  },
  "bestOffer": {
    "platform": "stockx",
    "lowestAskUsd": 235,
    "productName": "Air Jordan 1 Retro High OG Bred Toe",
    "productUrl": "https://stockx.com/..."
  },
  "startedAt": 1753234000000
}
```

`status` moves through `pending -> running -> awaiting_confirmation -> settled | rejected`, or `failed` if the search itself errors out. `finishedAt` appears once it reaches a terminal state. There's no seller identity in this response, only the marketplace each offer was found on (`platform`) — that's shown on purpose, not hidden, since it's part of how a buyer trusts a match.

### POST `/intents/{id}/decision` — Accept or reject

```bash
curl -sS -X POST "https://api.kachy.app/intents/3fa85f64.../decision" \
  -H "Content-Type: application/json" \
  -d '{"decision": "accept"}'
```

Response:
```json
{ "ok": true, "status": "settled", "decision": "accept" }
```

`settled` here means the match was accepted, not that a purchase happened. No money moves and no escrow is touched by this call.

### GET `/intents/{id}/stream` — Live progress

```bash
curl -N "https://api.kachy.app/intents/3fa85f64.../stream" \
  -H "Accept: text/event-stream"
```

Events: `offers` (a platform's result lands), `best` (the running best offer changes), `step` (a new trace line), `status` (state changes), and a final `complete` carrying the full state.

## Completing a purchase

The search API above never moves money. Once a search settles, the Kachy web app presents the available fulfillment route, provider, total price, verification method, and payment protection. A human signs in and approves the offer. If that route uses Kachy escrow, the app locks USDC, settles after fulfillment is confirmed, and refunds according to the escrow state machine. Partner-owned checkout routes follow the partner protection shown on the offer.

There's no agent-authenticated way to trigger fulfillment directly yet. Hand the accepted offer to a human through `/app`. The long-term goal is dependable mandate completion through approved partner APIs and fulfillment adapters, not unapproved browser checkout.

## Errors

| Status | Meaning |
|--------|---------|
| 400 | Malformed body, a required field is missing or the wrong type |
| 404 | Search id doesn't exist, or was evicted after its 30-minute / 2-hour window |
| 409 | `/decision` called before the search reached `awaiting_confirmation` |
| 5xx | Something broke on our end, retry with backoff |

Body shape:
```json
{ "error": "not awaiting confirmation", "currentStatus": "running" }
```

## Notes for agent builders

- No API key, and no rate limiting yet. Please be a good citizen. This is a small server, not built to take a flood of requests.
- Nothing persists. Don't build anything that assumes a search id is still valid after a couple of hours.
- Source marketplaces are named directly in the response (`stockx`, `bunjang`, `grailed`). That's intentional, not an oversight. It's part of how Kachy earns a buyer's trust in a match, so treat it as a stable part of the shape, not an implementation detail.
