# FindFetcher API and MCP server

Create and manage Australian price, ticket, experience and car-listing watches for a FindFetcher user, and read the matches. Prices are in AUD dollars. Beta.

- REST API: https://findfetcher.com.au/api/v1 (OpenAPI 3.1 at https://findfetcher.com.au/openapi.json)
- MCP server: https://findfetcher.com.au/api/mcp (Streamable HTTP). Tools: `create_price_watch`, `list_price_watches`, `get_matches`, `edit_price_watch`, `cancel_price_watch`, `resume_price_watch`, `switch_price_watch_cadence`, `create_gift_finder`, `edit_gift_ideas`.
- Auth: OAuth 2.1 authorization code with PKCE (S256) and dynamic client registration.

## Quickstart

### Fastest: connect the MCP server

Add https://findfetcher.com.au/api/mcp as a custom connector (remote MCP, Streamable HTTP) in Claude, ChatGPT, Cursor, VS Code or any MCP client. The client registers itself and opens FindFetcher so the human can log in and approve. Nothing else to set up.

### 1. Register your client (RFC 7591)

Public clients use PKCE and need no secret.

```
curl -X POST https://findfetcher.com.au/api/oauth/register \
  -H "Content-Type: application/json" \
  -d '{"client_name":"My assistant","redirect_uris":["https://example.com/callback"],"token_endpoint_auth_method":"none"}'
```

### 2. Send the human to approve

Open this URL in their browser with a PKCE S256 challenge. They log in to FindFetcher (or start a free trial) and approve the scopes.

```
https://findfetcher.com.au/api/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&scope=agent%3Aread%3Afetches%20agent%3Awrite%3Afetches%20agent%3Aread%3Amatches&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256&state=$STATE
```

### 3. Exchange the code for tokens

Access tokens last 1 hour. Refresh tokens rotate on every use.

```
curl -X POST https://findfetcher.com.au/api/oauth/token \
  -d grant_type=authorization_code \
  -d code=$CODE \
  -d redirect_uri=https://example.com/callback \
  -d client_id=$CLIENT_ID \
  -d code_verifier=$CODE_VERIFIER
```

### 4. Call the API

List the human's price watches.

```
curl https://findfetcher.com.au/api/v1/price-watches \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

## Scopes

- `agent:read:fetches`
- `agent:write:fetches`
- `agent:read:matches`
- `agent:read:account`
- `agent:read:webhooks`
- `agent:write:webhooks`

## API reference

### List price watches

`GET /api/v1/price-watches` (scope `agent:read:fetches`, operationId `listPriceWatches`)

Every price watch on the human's account, newest first, with prices in AUD dollars.

```
curl https://findfetcher.com.au/api/v1/price-watches \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

### Create a price watch

`POST /api/v1/price-watches` (scope `agent:write:fetches`, operationId `createPriceWatch`)

Start watching for something in Australia (a product, tickets, an experience or a car). Uses one of the human's plan slots. Put the budget in constraints.maxPrice (AUD dollars). The first check runs within minutes.

```
curl -X POST https://findfetcher.com.au/api/v1/price-watches \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"queryText":"Sony WH-1000XM5","category":"retail","constraints":{"maxPrice":400}}'
```

### Edit, pause or resume a price watch

`PATCH /api/v1/price-watches/{id}` (scope `agent:write:fetches`, operationId `updatePriceWatch`)

Change the name, query or constraints, or set status to "paused" or "active".

```
curl -X PATCH https://findfetcher.com.au/api/v1/price-watches/3f1c2b7e-0000-4000-8000-000000000000 \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status":"paused"}'
```

### Cancel a price watch

`DELETE /api/v1/price-watches/{id}` (scope `agent:write:fetches`, operationId `cancelPriceWatch`)

Stops the watch and frees its plan slot. Matches already found are kept.

```
curl -X DELETE https://findfetcher.com.au/api/v1/price-watches/3f1c2b7e-0000-4000-8000-000000000000 \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

### List matches for a price watch

`GET /api/v1/price-watches/{id}/matches` (scope `agent:read:matches`, operationId `listPriceWatchMatches`)

The listings FindFetcher found for this watch, with price, store and link.

```
curl https://findfetcher.com.au/api/v1/price-watches/3f1c2b7e-0000-4000-8000-000000000000/matches \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

### Switch a price watch between daily and premium checks

`POST /api/v1/price-watches/{id}/cadence` (scope `agent:write:fetches`, operationId `setPriceWatchCadence`)

"hourly" moves the watch to the Pro premium slot (checked every few hours); "daily" moves it back.

```
curl -X POST https://findfetcher.com.au/api/v1/price-watches/3f1c2b7e-0000-4000-8000-000000000000/cadence \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"cadence":"daily"}'
```

### Create a gift finder

`POST /api/v1/gift-finders` (scope `agent:write:fetches`, operationId `createGiftFinder`)

Describe who a gift is for, what they love and the budget; FindFetcher suggests gift ideas and watches Australian stores for them. Returns 200 with GiftFinderMissing when the budget or occasion date is needed: ask the human, then call again.

```
curl -X POST https://findfetcher.com.au/api/v1/gift-finders \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"brief":"My dad, 60s, loves gardening and good coffee, for his birthday","budget_aud":80,"occasion_date":"2026-12-20"}'
```

### Edit a gift finder's ideas

`PATCH /api/v1/gift-finders/{id}` (scope `agent:write:fetches`, operationId `editGiftFinderIdeas`)

Add, remove, pin or unpin gift ideas, or refresh the list.

```
curl -X PATCH https://findfetcher.com.au/api/v1/gift-finders/3f1c2b7e-0000-4000-8000-000000000000 \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"op":"add_idea","label":"pour-over coffee set"}'
```

### List webhook endpoints

`GET /api/v1/webhook-endpoints` (scope `agent:read:webhooks`, operationId `listWebhookEndpoints`)

The endpoints this assistant registered for match.created events. Signing secrets are never listed.

```
curl https://findfetcher.com.au/api/v1/webhook-endpoints \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

### Register a webhook endpoint

`POST /api/v1/webhook-endpoints` (scope `agent:write:webhooks`, operationId `createWebhookEndpoint`)

Receive HMAC-signed match.created events at a public HTTPS URL. The signing_secret is returned once, here. Up to 3 per human.

```
curl -X POST https://findfetcher.com.au/api/v1/webhook-endpoints \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/findfetcher-webhook"}'
```

### Enable, disable or rotate a webhook endpoint

`PATCH /api/v1/webhook-endpoints/{id}` (scope `agent:write:webhooks`, operationId `updateWebhookEndpoint`)

Set enabled, and/or rotate_secret to get a new signing secret (returned once).

```
curl -X PATCH https://findfetcher.com.au/api/v1/webhook-endpoints/3f1c2b7e-0000-4000-8000-000000000000 \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"rotate_secret":true}'
```

### Delete a webhook endpoint

`DELETE /api/v1/webhook-endpoints/{id}` (scope `agent:write:webhooks`, operationId `deleteWebhookEndpoint`)

Stops deliveries to this URL.

```
curl -X DELETE https://findfetcher.com.au/api/v1/webhook-endpoints/3f1c2b7e-0000-4000-8000-000000000000 \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

## Errors

Every error is JSON with `error_code`, `message`, `hint` and `docs_url`.

| Code | Status | Meaning | What to do |
|---|---|---|---|
| `missing_token` | 401 | No access token was sent. | Send an OAuth 2.1 access token as "Authorization: Bearer <token>". Get one from /api/oauth/token after the human approves your client. |
| `invalid_token` | 401 | The access token is invalid, expired or revoked. | Refresh it with your refresh_token at /api/oauth/token, or restart the OAuth flow if the human disconnected your assistant. |
| `token_revoked` | 401 | The human disconnected this assistant, so its tokens no longer work. | Restart the OAuth flow and ask the human to approve your assistant again. |
| `insufficient_scope` | 403 | The access token does not include the scope this endpoint needs. | Request the scope named in the WWW-Authenticate header when you start the OAuth flow. |
| `rate_limited` | 429 | Too many requests. | Wait for retry_after_seconds (or the Retry-After header) before trying again. |
| `invalid_request_body` | 400 | The request body is missing, is not valid JSON, or does not match the schema. | Send a JSON body that matches the request schema in /openapi.json. |
| `invalid_status_value` | 400 | The status value is not allowed. | Use "active" or "paused". |
| `missing_price_watch_id` | 400 | The price watch id is missing from the URL. | Call /api/v1/price-watches/{id} with an id from GET /api/v1/price-watches. |
| `missing_gift_finder_id` | 400 | The gift finder id is missing from the URL. | Call /api/v1/gift-finders/{id} with the id returned when the gift finder was created. |
| `missing_webhook_endpoint_id` | 400 | The webhook endpoint id is missing from the URL. | Call /api/v1/webhook-endpoints/{id} with an id from GET /api/v1/webhook-endpoints. |
| `not_found` | 404 | Nothing exists at this API path, or the record does not belong to this user. | Check the path against /openapi.json and use ids returned by the list endpoints. |
| `webhook_endpoint_not_found` | 404 | No webhook endpoint with this id belongs to this user. | List endpoints with GET /api/v1/webhook-endpoints and use one of those ids. |
| `webhook_endpoint_limit` | 400 | This user already has the maximum number of webhook endpoints. | Delete an unused endpoint with DELETE /api/v1/webhook-endpoints/{id} first. |
| `subscription_required` | 403 | The human's FindFetcher account has no active plan. | Ask the human to choose a plan at plans_url. An assistant cannot subscribe or pay. |
| `slot_limit` | 403 | All of the active fetch slots on this plan are in use. | Pause or cancel a price watch, or ask the human to add a slot or change plan at plans_url. |
| `fetch_limit` | 403 | All of the active fetch slots on this plan are in use. | Pause or cancel a price watch, or ask the human to change plan at plans_url. |
| `cadence_not_allowed` | 403 | This check frequency is not included in the human's plan. | Use "daily", or ask the human to move to Pro at plans_url for the premium cadence. |
| `daily_create_limit` | 429 | This user has created the maximum number of watches for today. | Try again tomorrow (Australian time). |
| `gift_creation_unavailable` | 503 | Gift Finder creation is switched off right now. | Create a normal price watch instead, or try again later. |
| `agent_api_unconfigured` | 503 | The agent API is not configured on this server. | Try again later. Contact joey@findfetcher.com.au if it persists. |
| `internal_error` | 500 | Something went wrong on our side. | Retry once. Contact joey@findfetcher.com.au with the time of the request if it keeps failing. |

## Rate limits and cost

- 120 requests per minute per client, and 60 per minute per human.
- 5 watch creations per minute per human, and 30 per day.
- Every response carries RateLimit-Policy and RateLimit headers (IETF draft-ietf-httpapi-ratelimit-headers), plus RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds), for the bucket closest to running out. Slow down as RateLimit-Remaining nears 0.
- A 429 response carries a Retry-After header and retry_after_seconds in the body.
- No per-call charges during the beta. Watches use the human's normal plan slots.

## Versioning and deprecation

- The version is in the URL: /api/v1. The MCP server follows the same rules for its tools.
- Within v1, changes are additive only: new endpoints, new optional request fields, new response fields and new error codes. Ignore fields you do not know.
- A breaking change ships as a new version (/api/v2). The old version keeps working for at least 90 days after the new one launches.
- From the day an endpoint or version is deprecated, its responses carry a Deprecation header (RFC 9745), a Sunset header with the shutdown date (RFC 8594) and a Link header with rel="deprecation" pointing at the migration notes.
- Every change is announced on /developers and in /agents.md first. The API is in beta, but these rules already apply.

## API keys and sandbox

There are no API keys: every call acts for one human who approved your client through OAuth, so access is always theirs to give and take back. There is no separate sandbox. To test end to end, start a 14-day free trial with your own account and connect to it.

## Machine-readable files

- [OpenAPI 3.1](https://findfetcher.com.au/openapi.json)
- [MCP server card](https://findfetcher.com.au/.well-known/mcp/server-card.json)
- [API catalog (RFC 9727)](https://findfetcher.com.au/.well-known/api-catalog)
- [OAuth authorization server metadata](https://findfetcher.com.au/.well-known/oauth-authorization-server)
- [OAuth protected resource metadata](https://findfetcher.com.au/.well-known/oauth-protected-resource)
- [Agent integration manual (Markdown)](https://findfetcher.com.au/agents.md)
- [llms.txt](https://findfetcher.com.au/llms.txt)
