# Run product research (Product Hunt)

> Eto API reference for `POST /api/v1/product-hunt`. Base URL `https://eto.tools/api/v1/`. Authenticate with the `X-Eto-API-Key` header.

Part of the [Eto API reference](https://eto.tools/dev/docs/). The full reference in one file is at [llms-full.txt](https://eto.tools/dev/docs/llms-full.txt); the machine-readable schema is at [openapi.json](https://eto.tools/dev/docs/openapi.json).

## POST /api/v1/product-hunt

Run product research (Product Hunt)

- Operation ID: `product_hunt`
- Slug: `product-hunt`
- Category: Research
- Authentication: API key only. Reads or writes data Eto holds for your own account.
- HTML: https://eto.tools/dev/docs/#ep-product-hunt
- Markdown: https://eto.tools/dev/docs/research/product-hunt.md

Run the full Eto Product Hunt for a keyword — the exact same engine as the Product Hunt page: search Etsy -> POPULAR-NOW detection -> demand (units sold/24h) -> AI ranking.

THREE modes:
  1. SEARCH = THE DEFAULT. Use this ANY time the user names a topic to research ("research t-shirts, 100 products, optimized"). Pass ONLY "keyword" + "amount" (+ optional "max_listing_age", "optimized", "full_details"). Eto does the whole pipeline and returns ranked products with real demand.
  2. "listing_ids": ONLY when you ALREADY have specific Etsy listing ids to score.
  3. "listings": ONLY when you already have full listing objects (Eto just adds demand).

CRITICAL: for a topic search, do NOT first call listings_search and then pass its listing_ids here. That bypasses popular-now detection and demand comes back EMPTY (null). Just call SEARCH mode with the keyword and let Eto do it.

demand_24h (units sold in last 24h) is the popularity signal — only populated via the real pipeline (SEARCH mode, or popular listings). With "optimized" true, results are AI-ranked by opportunity and carry ai_score + ai_reason; otherwise sorted by demand. Send native JSON types (optimized true, not "true"); stringified values are tolerated. Note: Etsy calls may be billed against your plan usage.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `keyword` | body | `string` | yes | Search term / keyword (max 200 chars). |
| `amount` | body | `integer` | no | SEARCH mode: how many products to fetch & rank (1-8000, default 50). Large amounts take longer — demand extraction is heavy. |
| `max_listing_age` | body | `integer` | no | SEARCH mode: only listings created within the last N months (0 or omit = no limit, max 60). |
| `optimized` | body | `boolean` | no | SEARCH mode: AI-powered filtering & ranking. When true, results carry ai_score + ai_reason and full_details is ignored. |
| `full_details` | body | `boolean` | no | SEARCH mode (only when optimized is false): true returns full per-listing details; false returns listing_id + demand only (lightest). |
| `listing_ids` | body | `array` | no | Array of Etsy listing ids (integers). Use instead of a keyword search. |
| `listings` | body | `array` | no | Array of listing objects you already have. Eto just adds demand. |

### Request

```bash
curl -X POST "https://eto.tools/api/v1/product-hunt" \
  -H "X-Eto-API-Key: eto_your_key" \
  -H "Content-Type: application/json" \
  -d '{
  "keyword": "leather wallet"
}'
```

```python
import requests

url = "https://eto.tools/api/v1/product-hunt"
headers = {"X-Eto-API-Key": "eto_your_key"}
payload = {
    "keyword": "leather wallet"
}

response = requests.post(url, headers=headers, json=payload, timeout=120)
response.raise_for_status()
print(response.json())
```

```javascript
const url = "https://eto.tools/api/v1/product-hunt";
const payload = {
  "keyword": "leather wallet"
};

const response = await fetch(url, {
  method: "POST",
  body: JSON.stringify(payload),
  headers: {
    "X-Eto-API-Key": "eto_your_key",
    "Content-Type": "application/json",
  },
});
if (!response.ok) throw new Error(`Eto API ${response.status}`);
const data = await response.json();
```

### Response

`application/json`. This endpoint has no worked response example in the registry yet; the [HTML reference](https://eto.tools/dev/docs/#ep-product-hunt) shows the fields it returns.

### Errors

Failures answer with the shared error body described in the Errors section: `{"error": {"code", "status", "message", "hint", "docs"}}`. Branch on `code`, not on the message.
