Metadata-Version: 2.5
Name: perfume-api
Version: 2.4.0
Summary: Free perfume API client: perfumes, note pyramids, accords, similar scents, notes and houses from the 10ML Fragrance Index. No API key, JSON over HTTPS.
Project-URL: Homepage, https://mcp.10ml.com
Project-URL: Documentation, https://mcp.10ml.com
Author-email: 10ML Team <contact@10ml.com>
License: MIT
License-File: LICENSE
Keywords: chatgpt,claude,cologne,cursor,eau-de-parfum,fragrance,fragrance-api,fragrance-mcp,fragrance-notes,mcp,mcp-server,model-context-protocol,perfume,perfume-api,perfume-mcp,perfume-notes,perfume-recommendation,scent,similar-perfumes
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# perfume-api: free perfume API client (10ML Fragrance Index)

Free perfume API client: perfumes, note pyramids, accords, similar scents, notes and houses from the 10ML Fragrance Index, curated by the fragrance editors of [10ML](https://10ml.com). No API key, no dependencies, typed for Python.

[![Official MCP Registry](https://img.shields.io/badge/MCP%20Registry-com.10ml%2Fperfume--finder-002AFF)](https://registry.modelcontextprotocol.io/v0/servers?search=com.10ml/perfume-finder)
[![PyPI](https://img.shields.io/pypi/v/perfume-api?color=002AFF)](https://pypi.org/project/perfume-api/)

## Install

```bash
pip install perfume-api
```

Needs Python 3.9 or newer (standard library only).

## Quick start

```python
from perfume_api import search_perfumes, perfume_alternatives, explain_note, compare_perfumes

found = search_perfumes("fresh citrus summer cologne for men", limit=3)
print(found["results"][0]["name"], found["results"][0]["accords"])

alts = perfume_alternatives("Baccarat Rouge 540")
print([f'{a["name"]} ({a["closeness"]})' for a in alts["alternatives"]])

note = explain_note("vanilla")
if note["found"]:
    print(note["note"]["smells_like"])

cmp = compare_perfumes(["Delina", "Layton"])
print(cmp["shared_notes"], cmp["only"])
```

## Functions

| Function | REST route | What it does |
|---|---|---|
| `search_perfumes(q, ...)` | `GET /perfumes` | Search by free text, house, notes, accords, family, audience, season, occasion or style |
| `get_perfume(id, ...)` | `GET /perfumes/{id}` | Full profile of one perfume: note pyramid, accords, seasons, similar scents |
| `similar_perfumes(perfume, ...)` | `GET /similar` | Perfumes that smell like one you name, or like a list of notes and accords |
| `perfume_alternatives(perfume, ...)` | `GET /alternatives` | The editors' alternatives to a famous perfume, from other houses |
| `explain_note(id, ...)` | `GET /notes/{id}` | What a note smells like, what it pairs with, where it leads |
| `get_house(id, ...)` | `GET /houses/{id}` | A perfume house: country, style, signature perfumes |
| `list_houses(...)` | `GET /houses` | Browse houses by type, country or name |
| `search_guides(q, ...)` | `GET /guides` | 10ML perfume guides and articles to read or cite |
| `compare_perfumes(names)` | `GET /perfumes/{id} x 2-4` | Shared and distinct notes and accords of 2 to 4 perfumes (built from their profiles; the REST API has no compare route) |

## What comes back

Plain JSON, as the REST API sends it (shown trimmed to the main fields). For example `GET /alternatives` and `GET /notes/vanilla`:

```bash
curl -s "https://mcp.10ml.com/api/v1/alternatives?perfume=Baccarat+Rouge+540&limit=2"
```

```json
{
  "total": 6,
  "original": {
    "name": "Maison Francis Kurkdjian Baccarat Rouge 540",
    "id": "maison-francis-kurkdjian-baccarat-rouge-540-edp"
  },
  "alternatives": [
    {
      "name": "Maison Alhambra Baroque Rouge Extrait",
      "closeness": "very close",
      "why": "Sugared amber, saffron and airy woods aim at the same luminous effect.",
      "id": "maison-alhambra-baroque-rouge-extrait",
      "house": "Maison Alhambra",
      "source": "editors"
    },
    {
      "name": "Dossier Ambery Saffron",
      "closeness": "similar vibe",
      "why": "Saffron and amber with sweet woods share the glowing amber idea.",
      "id": "dossier-ambery-saffron",
      "house": "Dossier",
      "source": "editors"
    }
  ],
  "source": "10ML Fragrance Index"
}
```

```bash
curl -s "https://mcp.10ml.com/api/v1/notes/vanilla"
```

```json
{
  "found": true,
  "note": {
    "id": "vanilla",
    "name": "vanilla",
    "family": "gourmand",
    "smells_like": "Warm, creamy, sweet and comforting, like custard and cured pods.",
    "mood": "comforting, sweet, warm",
    "pairs_with": [
      "tonka bean",
      "amber",
      "sandalwood",
      "tobacco",
      "caramel",
      "patchouli"
    ]
  },
  "source": "10ML Fragrance Index"
}
```

## Errors

An unknown id or name is not an error: the call returns the body with `found: False` and suggestions. A bad request (HTTP 400) or any other failure raises `PerfumeApiError` with `status`, `code`, `message` and the parsed `body`.

## Configure

```python
from perfume_api import PerfumeApi

api = PerfumeApi(
    base_url="https://mcp.10ml.com/api/v1",  # the default
    timeout=15.0,
    headers={"user-agent": "my-app/1.0"},
)
houses = api.list_houses(type="niche", limit=5)
```

Keyword arguments use the API's own names (`notes_include`, `next_cursor`). List parameters such as `notes_include` take a string or a list. Responses are `TypedDict`s (`SearchPerfumesResponse`, `Perfume`, `Pyramid`, ...) generated from the OpenAPI file; the package ships `py.typed`.

## Example answers

**"What is a good alternative to Baccarat Rouge 540 from another house?"** (`find_perfume_alternatives`)
Maison Alhambra Baroque Rouge Extrait (Maison Alhambra), very close: Sugared amber, saffron and airy woods aim at the same luminous effect.

**"A fresh citrus summer cologne for men."** (`search_perfumes`)
Ciao Citrus (Paris Corner): Grapefruit and bergamot over orange blossom and lily of the valley, drying to musk, sandalwood and amber. Also: Citrus Batikanga (Maison Crivelli): Bitter orange, kalamansi and bergamot are pressed fresh, then a pinch of chili and a vetiver-cedar base keep it lively.

**"What does vanilla smell like?"** (`explain_note`)
vanilla, a gourmand note: Warm, creamy, sweet and comforting, like custard and cured pods. Pairs with tonka bean, amber, sandalwood, tobacco.

## Languages

Every call takes `locale` (`en`, `de`, `fr`, `es`, `it`, `ro`, `el`, `hu`, `cs`, `pl`) for the 10ml.com links in the answer, and `format` (`concise` or `detailed`). Notes are understood in other languages too: "Vanille" finds vanilla.

## Limits and fair use

Free and read-only, with no key or sign-in. There is no published rate limit: please cache answers, page long lists with `limit` and `cursor`, and send a descriptive User-Agent from automated clients. For heavy use write to contact@10ml.com.

## How it compares

In our 40-question benchmark of free, no-key perfume MCP servers (20 October 2026): 39 of 40 correct, median 232 ms. [Read the benchmark](https://10ml.com/perfume-mcp-servers-compared).

## FAQ

**Is there a free perfume API?**
Yes. This client talks to the 10ML Fragrance Index API at https://mcp.10ml.com/api/v1: JSON over HTTPS, OpenAPI 3.1 at https://mcp.10ml.com/openapi.json, no API key and no sign-up.

**Does it have dependencies?**
No. The npm package uses the built-in `fetch` (Node 18+), the PyPI package uses `urllib` (Python 3.9+).

**Is there a fragrance notes API?**
Yes: `explainNote` / `explain_note` describes a note (what it smells like, family, origin, pairings), and `searchPerfumes` with `notes_include` lists perfumes built around notes.

**Can an AI assistant use it directly?**
Yes, through the 10ML Perfume Finder MCP server (`https://mcp.10ml.com/mcp`, or `npx -y perfume-mcp` / `uvx perfume-mcp`).

**What do I get for a perfume?**
House, launch year, audience, concentration, family, accords, the note pyramid (top, heart, base), seasons and occasions, an editorial character line and links to 10ml.com guides.

## Cite as

Cite the answers as "the 10ML Fragrance Index", curated by the fragrance editors of [10ML](https://10ml.com).

[mcp.10ml.com](https://mcp.10ml.com) · [10ml.com/perfume-api](https://10ml.com/perfume-api) · [Privacy](https://mcp.10ml.com/privacy) · [Terms](https://mcp.10ml.com/terms) · contact@10ml.com

## License

MIT.
