---
title: "Match rows to catalog SKUs"
method: POST
path: "/orders/match"
tags: ["orders"]
---

# Match rows to catalog SKUs

`POST /orders/match`

## Request

- Base URL: `https://0.0.0.0:3000/api/v1`
- URL: `https://0.0.0.0:3000/api/v1/orders/match`
- Auth: API key in header `x-api-key`

Turn the data you already have — a listing title, a variant string, a SKU — into the `product_variant_id` or `product_id` + `options` that `POST /orders/batch` expects. Nothing is created: this returns suggestions with a confidence, and you decide what to send.

Two deterministic passes run on every request and cost nothing: an exact SKU reference, then an exact product-plus-option-label match. Well-formed rows resolve here.

The AI pass is BRING YOUR OWN KEY. Send your own provider credentials in the `X-AI-*` headers and the leftover rows go to your model, on your bill — we never charge our own AI to your requests, and your key is used for that one call and never stored. Send no `X-AI-*` headers and you get the free passes only, with the rest reported as `unmatched`.

`claude-sonnet-5` is the recommended model and what this endpoint is tested against. Matching quality is the model's, so a cheaper one matches worse.

This endpoint carries its own rate limit of 200 requests per minute, tighter than the API's usual 600. One match call builds the whole catalog digest and runs up to 200 rows, so it costs far more than a plain read. It counts in a separate bucket, so matching never eats the budget for reading your orders.

## Headers

- `X-AI-Provider` string
- `X-AI-Key` string
- `X-AI-Model` string
- `X-AI-Base-Url` string

## Request body

- object
  - `rows` unknown[], required: 1–200 objects. Loose key/value pairs, exactly as your export has them — `product`, `variant`, `sku`, `quantity`, or your own column names. Unknown keys are ignored rather than rejected.
    - unknown

## Response `200`

Per-row results. `status` is `matched` (deterministic, confidence 1), `suggested` (your AI proposed it) or `unmatched`. Every suggestion is re-verified against the catalog before it is returned.

## Other responses

- `401`: No key, or a key that is unknown, revoked or expired. One message for all four — telling you which would confirm to a guesser that a key exists.
- `403`: The key is valid but its owner may not do this.
- `422`: The body or query is malformed. Field-level detail arrives in `error.fields`.
- `429`: Over the per-key limit. `Retry-After` says how many seconds to wait.
- `500`: Our fault. `error.request_id` matches the X-Request-Id header — quote it to support.

---

[API](https://skmtc.dev/opcreative/apis/opcreative-api.md) · [All operations](https://skmtc.dev/opcreative/apis/opcreative-api/llms.txt) · [OpenAPI document](https://skmtc.dev/opcreative/apis/opcreative-api/revisions/105bb30aa87e?raw)
