Category Finder API
Send product titles, get back the Google product category for each: the numeric ID, the full path and a confidence level. Calling it from your own code takes a WISEPIM API key, which comes with the Pro plan.
Want to try it first? The free browser tool runs the same endpoint for up to 5 products a day, no account or key needed.
Open the Product Category FinderEndpoint
POST https://wisepim.com/api/public/category-finderAuthentication
Send a WISEPIM project API key as a Bearer token. Keys start with wpk_. Any live project key works; the endpoint needs no particular scope. API keys are part of the Pro plan.
Authorization: Bearer wpk_...Without a key, every request needs a Cloudflare Turnstile token, which only the browser tool can produce. Scripts and servers need a Pro plan API key. A Bearer value that is not a live key gets a 401; it does not fall back to the anonymous tier.
API keys come with the Pro plan and belong to a WISEPIM project. Project owners and admins create them in Settings > API access: name the key, keep the default read permissions, and copy it right away, because WISEPIM shows the full key only once. Keep the key on your server: never ship it in browser code.
Limits
| Limit | With a Pro plan API key | Without a key (browser) |
|---|---|---|
| Products per request | 20 | 5 |
| Products per day (UTC) | 100 per key | 5 per visitor |
| Requests per minute | The key's own limit (600 by default) | Not applicable |
- Limits count products, not requests: a request with 3 titles uses 3.
- Daily counters reset at 00:00 UTC.
- All callers, with or without a key, also share one daily Category Finder budget. When it is spent, every caller gets
daily_budget_exhausteduntil midnight UTC. - A request that fails with 503 is not counted.
Request
A JSON body with Content-Type: application/json. Unknown keys are ignored.
| Field | Type | Description |
|---|---|---|
items | array, required | 1 to 20 products with an API key (1 to 5 without one). More is a 400. |
items[].title | string, required | The product title, 3 to 300 characters after trimming. |
items[].description | string, optional | Up to 2,000 characters. Helps when the title is short or ambiguous. |
locale | string, optional | One of "en", "nl", "de", "fr", "es". Default "en". Sets the language of reason, and of google.path for "nl". |
turnstileToken | string, browser only | A Cloudflare Turnstile token. Required without an API key; ignored when a valid key is sent. |
curl -X POST https://wisepim.com/api/public/category-finder \
-H "Authorization: Bearer $WISEPIM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"locale": "en",
"items": [
{ "title": "Velvet 3-seater sofa, emerald green" },
{ "title": "Stainless steel French press, 1 litre",
"description": "Double-walled coffee maker with a 4-level filter." }
]
}'const response = await fetch("https://wisepim.com/api/public/category-finder", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.WISEPIM_API_KEY}`,
},
body: JSON.stringify({
locale: "en",
items: [{ title: "Velvet 3-seater sofa, emerald green" }],
}),
});
if (response.status === 429) {
// Every 429 carries Retry-After: wait that many seconds before retrying.
const waitSeconds = Number(response.headers.get("Retry-After") ?? "60");
throw new Error(`Rate limited, retry in ${waitSeconds}s`);
}
const data = await response.json();
if (!response.ok) throw new Error(data.error);
const matches = data.results.map((result) => ({
input: result.input,
// google is null when no category could be confirmed in Google's file.
googleId: result.google?.id ?? null,
confidence: result.confidence,
}));Response
200 OK with one result per product. The reasons below are illustrative; the IDs and paths are real entries from Google's file.
{
"results": [
{
"input": "Velvet 3-seater sofa, emerald green",
"google": { "id": "460", "path": "Furniture > Sofas" },
"gpc": null,
"confidence": "high",
"reason": "A three-seat upholstered sofa fits Sofas directly."
},
{
"input": "Stainless steel French press, 1 litre",
"google": {
"id": "1557",
"path": "Home & Garden > Kitchen & Dining > Kitchen Appliances > Coffee Makers & Espresso Machines > French Presses"
},
"gpc": null,
"confidence": "high",
"reason": "The title names a French press coffee maker."
}
],
"remaining": { "today": 98 },
"taxonomyVersion": "google-2021-09-21"
}| Field | Type | Description |
|---|---|---|
results | array | One result per item, in the order you sent them. |
results[].input | string | The title you sent, trimmed. |
results[].google | object or null | id (numeric string) and path from Google's product taxonomy. Both are checked against Google's published file; when the match cannot be confirmed this is null, never an invented ID. |
results[].gpc | null | Reserved for a GS1 GPC brick. Always null today: no verified GPC dataset backs it yet. |
results[].confidence | string | "high", "medium" or "low". Always "low" when google is null. |
results[].reason | string | One sentence in the request locale explaining the match, or why there is none. |
remaining.today | integer | Products you can still classify today (UTC). |
taxonomyVersion | string | The Google taxonomy file the IDs were checked against, for example "google-2021-09-21". |
Errors
Errors are JSON with an error code. Optional keys are left out, never sent as null. Every 429 sends a Retry-After header (seconds) and a matching retryAfterSeconds.
| Status | error | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed JSON, a field out of range, or too many items for your tier. |
| 401 | invalid_api_key | The Bearer value is not a live WISEPIM API key (unknown, revoked or expired). |
| 403 | turnstile_failed | No API key and the Turnstile token is missing or invalid. |
| 429 | rate_limited | Your daily product limit, or your key's per-minute request limit, is used up. The daily refusal also carries remaining.today. |
| 429 | daily_budget_exhausted | The Category Finder's shared daily budget is spent for everyone. It resets at 00:00 UTC. |
| 503 | unavailable | The AI provider or a dependency failed. The products are not counted against your limit; retry later. |
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
{ "error": "rate_limited", "retryAfterSeconds": 3600, "remaining": { "today": 0 } }Google taxonomy version
IDs and paths come from the Google product taxonomy files with IDs that Google publishes, version 2021-09-21. The version is returned in taxonomyVersion on every response, so you can tell when it changes.
Results are checked against Google's English (en-US) and Dutch (nl-NL) files. With "nl" you get the Dutch path; with "en", "de", "fr" or "es" you get the English path. The ID is the same in every language, so send the ID in your google_product_category feed attribute.
Titles are used to answer the request. The finder's usage analytics record counts, never titles, and your IP address is hashed for the daily limit, never stored as is.
Something not documented?
Tell us what you are building and we will point you to the right endpoint, or add it.
