{
  "schema_version": 2,
  "id": "develop/ai/context-engine/intent-router/match",
  "title": "Match queries to intents",
  "url": "https://redis.io/docs/latest/develop/ai/context-engine/intent-router/match/",
  "summary": "Classify a query into intents, read scores and targets, and tune thresholds and result limits.",
  "tags": [
    "docs",
    "develop",
    "ai"
  ],
  "last_updated": "2026-10-26T19:17:37-07:00",
  "page_type": "content",
  "content_hash": "33699a7c4964f00c656a7e91baaf26b15bcbf4b694a8307ba03385ad6aa5d2ba",
  "sections": [
    {
      "id": "code-examples",
      "title": "Code examples",
      "role": "example",
      "text": "These examples connect to Iris services using SDKs or HTTP requests with curl. Follow one language throughout each walkthrough.\n\n\nMatching is the step where Intent Router does its job at runtime. After you create a Router and give it intents with example phrases, your application sends each incoming user message to `match`. The Router returns the closest intents, each with a score and a target. Your application uses the top result to decide what happens next, such as sending an order question to an order-tracking agent and a refund request to a refunds workflow.\n\nThe examples use the connection values from the [quickstart](https://redis.io/docs/latest/develop/ai/context-engine/intent-router/quickstart#save-connection-values) and match against its `track_order` and `refund` intents. Each complete program creates those intents first, so it needs an agent key with `write` access. Matching on its own needs only `read` access. Choose one language tab and use it for the whole page."
    },
    {
      "id": "send-a-query",
      "title": "Send a query",
      "role": "content",
      "text": "A match request takes a query and returns the closest intents, best first.\n\n**Available in:** Python (Iris), TypeScript (Iris), cURL (Iris), CLI (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### TypeScript (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]\n\n##### CLI (Iris)\n\n[code example]\n\n\n\n| Field | Required | Meaning |\n| ----- | -------- | ------- |\n| `query` | Yes | The text to classify, 1 to 2,048 characters. |\n| `limit` | No | The most intents to return. Defaults to the deployment default, which is 5. |\n| `minScore` | No | The minimum score for every returned intent, from 0 to 1. Overrides each intent's own and the Router's `minScore`. |\n\nEach item in the response has:\n\n| Field | Meaning |\n| ----- | ------- |\n| `name` | The intent's name. |\n| `score` | How close the query is to the intent, from 0 to 1. Higher is closer. |\n| `description` | The intent's description, when it has one. |\n| `target` | The intent's target, when it has one. |\n| `metadata` | The intent's metadata, when it has any. |\n\nA query with no close intent returns `200` with an empty list."
    },
    {
      "id": "act-on-the-result",
      "title": "Act on the result",
      "role": "content",
      "text": "The Router returns the target and leaves the action to your application. A typical handler takes the top item, checks that one exists, and dispatches on `target.type`.\n\n**Available in:** Python (Iris), TypeScript (Iris), cURL (Iris), CLI (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### TypeScript (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]\n\n##### CLI (Iris)\n\n[code example]\n\n\n\nDecide in advance what your application does when the list is empty, such as asking a clarifying question or using a general-purpose agent. When two items score close together, the query may be ambiguous: consider asking the user instead of picking the first."
    },
    {
      "id": "control-relevance-and-result-count",
      "title": "Control relevance and result count",
      "role": "content",
      "text": "By default an intent's score is the similarity of its closest example to the query. The data plane reads the nearest examples, 50 by default, groups them by intent, and scores each group.\n\nThe minimum score for each intent comes from the first of these that is set:\n\n1. The `minScore` in the match request, which applies to every intent.\n1. The intent's own `minScore`, when it is above `0`.\n1. The Router's `minScore`, which is `0.7` unless you set another.\n\nHigher scores are stricter. Raise a threshold to reduce wrong matches and lower it to reduce misses.\n\n| Goal | Adjust |\n| ---- | ------ |\n| One intent matches too easily | Raise that intent's `minScore`. |\n| One intent misses real queries | Add examples that use the missed wording. Lower `minScore` only if examples don't help. |\n| Every intent is too strict or too loose | Change the Router's `minScore`. |\n| Compare scores while tuning | Match with `minScore` set to `0` to see the closest intents regardless of threshold, then pick thresholds. |\n\n**Available in:** Python (Iris), TypeScript (Iris), cURL (Iris), CLI (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### TypeScript (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]\n\n##### CLI (Iris)\n\n[code example]\n\n\n\n`limit` can't exceed the deployment cap, which defaults to 50. A request above the cap is rejected with `400` rather than clamped, so you never silently receive fewer ranked results than you asked for.\n\nScores aren't comparable across embedding models. A Router's model can't change, so recheck thresholds when you move to a Router with a different model."
    },
    {
      "id": "handle-errors",
      "title": "Handle errors",
      "role": "content",
      "text": "Match is a data-plane request, so it fails when its Router can't serve:\n\n- `400`: the query is empty or too long, or `limit` exceeds the cap.\n- `401`: the key is invalid, expired, or revoked.\n- `403`: the key has no `read` grant on the Router.\n- `409`: the Router is provisioning or not serving. Retry after a short wait.\n- `423`: an administrator suspended the Router.\n- `424`: the embedding provider is unavailable. Retry with backoff.\n- `503` with `Retry-After`: the Router's search index is rebuilding, or its database isn't answering. Retry after the interval. A Router that can't search never returns an empty list in place of an error.\n\nSee [service limits and errors](https://redis.io/docs/latest/develop/ai/context-engine/intent-router/limits) for all error types and retry guidance."
    }
  ],
  "examples": [
    {
      "id": "send-a-query-ex0",
      "language": "python",
      "code": "for item in router.match(\"where is my order please\", limit=3, min_score=0.6):\n    print(item.name, item.score, item.target)",
      "section_id": "send-a-query"
    },
    {
      "id": "send-a-query-ex1",
      "language": "typescript",
      "code": "const { result } = await router.match({\n  query: \"where is my order please\",\n  limit: 3,\n  minScore: 0.6,\n});\nfor (const item of result.items) console.log(item.name, item.score, item.target);",
      "section_id": "send-a-query"
    },
    {
      "id": "send-a-query-ex2",
      "language": "bash",
      "code": "ROUTER_API=\"${IRIS_INTENT_ROUTER_URL%/}/iris/data/v1/intent/routers/$IRIS_INTENT_ROUTER_ID\"\ncurl --fail-with-body --silent --show-error \\\n  -X POST \"$ROUTER_API/match\" \\\n  -H \"Authorization: Bearer $IRIS_INTENT_ROUTER_API_KEY\" \\\n  -H 'Content-Type: application/json' \\\n  --data '{\"query\": \"where is my order please\", \"limit\": 3, \"minScore\": 0.6}' \\\n  | jq -c '.items[] | {name, score, target}'",
      "section_id": "send-a-query"
    },
    {
      "id": "send-a-query-ex3",
      "language": "bash",
      "code": "iris intent-router match \"where is my order please\" --limit 3 --min-score 0.6",
      "section_id": "send-a-query"
    },
    {
      "id": "act-on-the-result-ex0",
      "language": "python",
      "code": "def route(query: str) -> str:\n    top = router.match(query).best\n    if top is None:\n        return \"no intent is close enough: use a default path\"\n    if top.target is None:\n        return f\"none {top.name}\"\n    return f\"{top.target.type} {top.target.ref}\"\n\n\nprint(route(\"where is my order please\"))",
      "section_id": "act-on-the-result"
    },
    {
      "id": "act-on-the-result-ex1",
      "language": "typescript",
      "code": "async function route(query: string): Promise<string> {\n  const { items } = (await router.match({ query })).result;\n  if (items.length === 0) return \"no intent is close enough: use a default path\";\n  const top = items[0];\n  if (!top.target) return `none ${top.name}`;\n  return `${top.target.type} ${top.target.ref}`;\n}\n\nconsole.log(await route(\"where is my order please\"));",
      "section_id": "act-on-the-result"
    },
    {
      "id": "act-on-the-result-ex2",
      "language": "bash",
      "code": "QUERY=\"where is my order please\"\ncurl --fail-with-body --silent --show-error \\\n  -X POST \"$ROUTER_API/match\" \\\n  -H \"Authorization: Bearer $IRIS_INTENT_ROUTER_API_KEY\" \\\n  -H 'Content-Type: application/json' \\\n  --data \"$(jq -n --arg query \"$QUERY\" '{query: $query}')\" > match.json\nif [ \"$(jq '.items | length' match.json)\" -eq 0 ]; then\n  echo \"no intent is close enough: use a default path\"\nelse\n  jq -r '.items[0] | if .target then \"\\(.target.type) \\(.target.ref)\" else \"none \\(.name)\" end' match.json\nfi",
      "section_id": "act-on-the-result"
    },
    {
      "id": "act-on-the-result-ex3",
      "language": "bash",
      "code": "iris --json intent-router match \"where is my order please\" > match.json\nif [ \"$(jq '.items | length' match.json)\" -eq 0 ]; then\n  echo \"no intent is close enough: use a default path\"\nelse\n  jq -r '.items[0] | if .target then \"\\(.target.type) \\(.target.ref)\" else \"none \\(.name)\" end' match.json\nfi",
      "section_id": "act-on-the-result"
    },
    {
      "id": "control-relevance-and-result-count-ex0",
      "language": "python",
      "code": "for item in router.match(\"i need help\", min_score=0):\n    print(item.name, round(item.score, 2))",
      "section_id": "control-relevance-and-result-count"
    },
    {
      "id": "control-relevance-and-result-count-ex1",
      "language": "typescript",
      "code": "const { result: candidates } = await router.match({ query: \"i need help\", minScore: 0 });\nfor (const item of candidates.items) console.log(item.name, item.score.toFixed(2));",
      "section_id": "control-relevance-and-result-count"
    },
    {
      "id": "control-relevance-and-result-count-ex2",
      "language": "bash",
      "code": "curl --fail-with-body --silent --show-error \\\n  -X POST \"$ROUTER_API/match\" \\\n  -H \"Authorization: Bearer $IRIS_INTENT_ROUTER_API_KEY\" \\\n  -H 'Content-Type: application/json' \\\n  --data '{\"query\": \"i need help\", \"minScore\": 0}' | jq -c '.items[] | {name, score}'",
      "section_id": "control-relevance-and-result-count"
    },
    {
      "id": "control-relevance-and-result-count-ex3",
      "language": "bash",
      "code": "iris intent-router match \"i need help\" --min-score 0",
      "section_id": "control-relevance-and-result-count"
    }
  ]
}
