FactionDocs
API Reference

Match Product

Map line-item descriptions to product IDs with substitutes and rationale.

POST
/match/product

Authorization

bearerAuth
AuthorizationBearer <token>

OAuth2 client credentials flow. Token obtained from /oauth2/token on the auth host.

In: header

Header Parameters

X-Correlation-Id*string

Caller-supplied correlation ID. Propagated end-to-end and returned in responses and audit logs.

Idempotency-Key?string

Optional. If reused within 24 hours with the same payload, the cached response is returned. Reuse with a different payload yields 409.

Prefer?string

Set to 'respond-async' to receive a 202 Accepted with a job ID instead of a synchronous result.

Value in"respond-async"

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://api.eu.faction.ai/v1/match/product" \  -H "X-Correlation-Id: 8f3c-b21" \  -H "Idempotency-Key: case-CRM-2026-04-29-00417-intent-v1" \  -H "Content-Type: application/json" \  -d '{    "case_id": "string",    "line_items": [      {}    ]  }'
{
  "matches": [
    {
      "input_description": "SKF 6205-2RS bearing",
      "rubix_product_id": "GB-BRG-6205-2RS",
      "quantity": 50,
      "confidence_score": 0.98,
      "match_rationale": "Manufacturer + part number exact match. Customer ordered same SKU 11 times in last 18 months.",
      "alternatives": [
        {
          "rubix_product_id": "GB-BRG-6205-2Z",
          "confidence_score": 0.71,
          "reason": "Same series, sealed variant"
        }
      ],
      "unmatched_flag": false
    },
    {
      "input_description": "Gates B-section drive belt B72",
      "rubix_product_id": "GB-BLT-GATES-B72",
      "quantity": 20,
      "confidence_score": 0.94,
      "match_rationale": "Manufacturer + size + section exact match.",
      "alternatives": [],
      "unmatched_flag": false
    }
  ],
  "unmatched": []
}
{
  "job_id": "job_01JZP4XYZ123",
  "status_url": "/v1/jobs/job_01JZP4XYZ123",
  "status": "queued"
}
{
  "error_code": "MISSING_REQUIRED_FIELD",
  "message": "Field 'case_id' is required.",
  "correlation_id": "8f3c-b21",
  "request_id": "req_01JZP4..."
}
{
  "error_code": "EXPIRED_TOKEN",
  "message": "Bearer token expired at 2026-04-29T13:00:00Z.",
  "correlation_id": "8f3c-b21",
  "request_id": "req_01JZP4..."
}
{
  "error_code": "SCOPE_INSUFFICIENT",
  "message": "Token lacks scope 'extract.quote'.",
  "correlation_id": "8f3c-b21",
  "request_id": "req_01JZP4..."
}
{
  "error_code": "IDEMPOTENCY_KEY_CONFLICT",
  "message": "Idempotency key was previously used with a different request payload.",
  "correlation_id": "8f3c-b22",
  "request_id": "req_01JZP4..."
}
{
  "error_code": "RATE_LIMITED",
  "message": "Per-tenant per-module rate limit exceeded.",
  "correlation_id": "8f3c-b21",
  "request_id": "req_01JZP4..."
}
{
  "error_code": "INTERNAL_ERROR",
  "message": "An unexpected error occurred.",
  "correlation_id": "8f3c-b21",
  "request_id": "req_01JZP4..."
}

Match paths

The matcher tries multiple paths and returns the strongest. The path that won is reported in match_rationale:

  1. Manufacturer + part number exact match.
  2. Manufacturer cross-reference (competitor part to Rubix-stocked equivalent).
  3. Semantic match (description embeddings against catalogue).
  4. Historical-pattern match (this customer ordered this SKU before).
  5. Branch-local override (branch-supplied spreadsheet).

Notes

  • match_strategy: strict, balanced (default), permissive. Controls how aggressively the matcher returns ambiguous candidates.
  • Unit-of-measure mismatch (e.g., "box of 50" vs. per-unit catalogue) triggers unit_conversion_required: true with a proposed conversion.
  • Discontinued SKU with a successor mapped: returns the successor with rationale.
  • Line-item count over 200 should use the async pattern.

On this page