# Filter, sort and facet product search with Affiliate.com's API

Affiliate.com's Product Search API narrows a search across all Affiliate.com merchants with filters, sort order, field selection and facets, all set in one `POST https://zeroclick.affiliate.com/zcj/oz78c65866wi/v1/products` request body. Each request costs $0.02, however many options it sets. Pay the request's 402 price quote from a wallet; no account or API key is needed.

## At a glance

- **Endpoint:** `POST https://zeroclick.affiliate.com/zcj/oz78c65866wi/v1/products`, which searches products
- **Body:** `search` (a string, or filter objects joined with AND) or `query` (rules that allow OR), plus `sort_by`, `sort_order`, `fields`, `facets`, `duplicate_fields_to_exclude`, `page`, `per_page` and `after`
- **Price:** $0.02
- **Payment:** each request answers with a 402 price quote, paid from a wallet over x402 or MPP, or drawn from prepaid Credits.
- **Response:** JSON with `data` (the products), `meta` (totals, paging, sort) and `facets` when you ask for them
- **Limits:** `per_page` defaults to 100. Past 10,000 results, page with the `after` cursor.
- **Full purchase flow:** [the buyer skill](https://zeroclick.affiliate.com/zcj/oz78c65866wi/SKILL.md)

## Search options in one request body

| You want | Field | Value |
| --- | --- | --- |
| A broad match across product fields | `search` | A string, such as `"wireless headphones"` |
| Precise filters, all of which must match | `search` | An array of `{"field", "operator", "value"}` objects |
| Either of two conditions | `query` | Rules joined by `rules_operator: "any"` |
| A sort order | `sort_by`, `sort_order` | A field such as `final_price`, `regular_price`, `sale_discount`, `name`, `brand`, `updated_at` or `relevance`, with `asc` or `desc` |
| A smaller response | `fields` | An array of the product fields you read |
| The spread of results | `facets` | `merchant`, `network` or `final_price`, the facets the API accepts |
| One result per value | `duplicate_fields_to_exclude` | One field name |
| More results | `page`, `per_page`, `after` | Numbers, or the cursor from `meta.after` |

Common filter fields are `any`, `name`, `brand`, `category`, `barcode`, `final_price`, `on_sale`, `availability`, `merchant.id` and `merchant.name`. The operator depends on the field type:

| Field type | Operators |
| --- | --- |
| Text, such as `brand` or `category` | `=`, `!=`, `LIKE`, `NOT LIKE` |
| Numeric, such as `final_price` | `=`, `!=`, `>`, `<`, `>=`, `<=` |

## Run a filtered, sorted, faceted search

1. Send the request. Unpaid, it returns a 402 price quote, with the amount in `payment.amountUsd`.

   ```bash
   curl -s -X POST https://zeroclick.affiliate.com/zcj/oz78c65866wi/v1/products \
     -H 'Content-Type: application/json' \
     -d '{"search": [{"field": "category", "operator": "LIKE", "value": "headphones"}, {"field": "final_price", "operator": "<=", "value": 100}], "sort_by": "final_price", "sort_order": "asc", "fields": ["name", "final_price", "currency", "merchant"], "facets": ["merchant"], "per_page": 3}'
   ```

2. Pay the 402 with an x402 or MPP client and retry the same body with the proof attached. This request paid $0.02.

3. Read the response. It has this shape:

   ```text
   data[]             name, final_price, currency, merchant {id, object_id, slug, name, logo_url, ...}
   meta               total, from, to, current_page, last_page, per_page, fields, after, facets, sort_by, sort_order, trace_id
   facets.merchants[] id, name, description, domains, icon_url, logo_url, product_count, origin_id
   links              self, first, prev, next
   ```

   The `merchant` facet comes back under `facets.merchants`, and `meta.facets` lists the facets you asked for. Keep `meta.trace_id` for support.

4. Narrow with what the facet shows. Add a filter on a merchant's `id` from `facets.merchants` and send the search again as a new paid request:

   ```json
   {"field": "merchant.id", "operator": "=", "value": <id from facets.merchants>}
   ```

## Combine conditions with OR

Filter objects in `search` always combine with AND. For OR, send `query` instead of `search`, since the two cannot be combined. Each rule holds `conditions` and a `conditions_operator` (`any` or `all`). `rules_operator` joins the rules: `any` means OR and `all` means AND, the default.

```json
{"query": {"rules_operator": "any", "rules": [
  {"conditions_operator": "all", "conditions": [{"field": "brand", "operator": "=", "value": "Sony"}]},
  {"conditions_operator": "all", "conditions": [{"field": "brand", "operator": "=", "value": "Bose"}]}
]}, "per_page": 3}
```

This request paid $0.02. Without `fields`, the response carries every product field for each product, and `meta.fields` lists them. Set `fields` to keep the response to what you read.

## Page through results

- `page` starts at 1. `per_page` sets the page size and defaults to 100.
- `meta` reports `total`, `current_page`, `last_page`, `from` and `to`.
- Past 10,000 results, send `after` with the `meta.after` value from the previous response.
- Each page is a separate request at the same price. Five pages cost $0.10.

## Limits

- `search` and `query` are mutually exclusive.
- `duplicate_fields_to_exclude` cannot be combined with `balanced_mix`.
- The API accepts the facets `network`, `merchant` and `final_price`, and answers 422 to others, such as `brand`.
- A paid request the API rejects with a 422 is still charged.
- Paying by card per call needs at least $0.50 per call, more than one search costs. To pay by card, buy prepaid Credits (from $0.50 by card) with an agent credential from [auth.md](https://zeroclick.affiliate.com/zcj/oz78c65866wi/auth.md), and let searches draw down the balance.

## Errors

| Response | Meaning | Action |
| --- | --- | --- |
| `402` | The price quote | Pay and retry the same body, or stop |
| `422` | The API rejected the request body. The request was charged. | Check the body against the reference, then send a corrected request |
| `400 invalid_request` | The body is malformed | Fix the JSON |
| `settlement_failed` | The payment did not settle; `settlementReason` says why | Fix the wallet and send the request again for a fresh 402 |
| `rate_limit_exceeded` | Too many requests | Wait `retry_after` or `Retry-After`, then retry |

## Frequently asked questions

### Can I filter products by brand and price in one request?

Yes. Put two filter objects in `search`, one on `brand` with `=` and one on `final_price` with `<=` or `>=`. Filters in `search` combine with AND, and the request costs $0.02 however many filters it holds.

### How do I search for products from either of two brands?

Send `query` instead of `search`, with one rule per brand and `rules_operator` set to `any`. Each rule needs its own `conditions_operator`.

### Which facets does the product search API return?

`merchant`, `network` and `final_price`. A `merchant` facet returns `facets.merchants`, a list of merchant objects with `id`, `name`, `domains` and `product_count`, which you can feed back as a `merchant.id` filter.

### Do filters, sorting or facets change the price?

No. Every request to `/v1/products` costs $0.02, whatever options it sets. Only the number of requests changes the total.

### How do I get more than 10,000 results?

Read `meta.after` from a response and send it as `after` in the next request. Each request returns one page and is paid on its own.

## Keep reading

- [Buyer skill](https://zeroclick.affiliate.com/zcj/oz78c65866wi/SKILL.md): the full purchase flow
- [API reference](https://zeroclick.affiliate.com/zcj/oz78c65866wi/openapi.json): every search field and response field
- [How to pay](https://zeroclick.affiliate.com/zcj/oz78c65866wi/payment.md): rails, credits and top-ups
- [Error reference](https://zeroclick.affiliate.com/zcj/oz78c65866wi/errors.md): every error code and what to do

---

Updated 2026-10-16. All Affiliate.com guides: https://zeroclick.affiliate.com/guides.md. Storefront: https://zeroclick.affiliate.com/zcj/oz78c65866wi/llms.txt.
