Skip to main content
GET
Recommend Markets

What this returns that browse does not

The rest of the catalog family is browse: you filter markets, products, or appetite cells and page through what matches. This endpoint answers a question instead. You describe a risk, in free text or in structured hints, and get back at most five markets in rank order, each with the evidence behind its position. It is synchronous and account-free. There is no run to submit, nothing to poll, and no account or prospect record involved. The resolution, the appetite gate, and the ordering are the same ones the placement engine applies internally, and this endpoint adds no judgment of its own on top of them: whether a market will write a class is decided in one place, by one piece of code, for both surfaces. Candidates are narrowed before the gate by the requested lines, a named excluded state, and the structured employee and premium bands. Nothing else narrows them. The free-text constraint columns are not interpreted here, because a value like “Nationwide” in an eligible-states list or “22 or more locations” in a prohibited-class list cannot be read with enough certainty to remove a market from a producer’s options. Use browse when you know what you are looking for. Use recommend when you have a risk and need to know where it can go.

Asking the question

At least one of q, naics, state, or lob must be present. Everything else is optional:
  • q is free text and is scanned for all three of state, line of business, and business class, so roofing contractor in Colorado needing general liability alone is a complete question.
  • naics takes a code at any grain. A code with no entry in the keyword bridge still resolves and is echoed back without a keyword rather than being dropped.
  • state accepts CO or Colorado.
  • lob is repeatable and classified through the shared line taxonomy, so everyday phrasing works: lob=commercial property resolves to the code property.
  • employees and premium_band (estimated annual premium in whole US dollars) are used only for positive exclusion. A product is dropped when its own stated band excludes the value. A product that states no band is never dropped, because absent data is not a statement of ineligibility.
Every request echoes what the platform understood on resolved, and names what it could not resolve on missing_context (state, line_of_business, business_class). A partial question still gets an answer built from the parts that did resolve; the caller decides whether the gap is worth a clarifying question.

What verified each match

match_status says what actually stands behind a recommendation, and confidence is a deterministic restatement of it rather than a separate score. Exactly one applies to each market. A line_level_only result is a real answer, not a failure: it says this market serves the line and nobody has recorded an appetite position on the class.

Reading the appetite block

appetite is present only when a current guide cell positively matched, and it is attributed to a document. Two things follow from that, and both matter: strength is the guide cell’s own eligibility word. It is not the product row’s served appetite strength, and the two can disagree. When a product row says restricted and the guide cell says acceptable, this block reports acceptable, because it is quoting the guide, not summarizing the market. If you need the served value, read the product row through the browse endpoints. line_code names which line the evidence covers. A market can come back with several products while the cell speaks to one line. The line named here is always present in products, even when the per-market product cap would otherwise have dropped it, so the claim is always checkable against a row you were given. Do not read the appetite block as coverage of every product listed. A market whose guide positively declined the resolved class and line is never recommended at all. Only a known decline word excludes: an unknown, empty, or newly added eligibility leaves the market in the answer as unassessed, because absence of a statement is not a decline.

Why a market ranks where it does

rank_reasons narrate only signals the ranking comparator actually read: appetite strength, then AM Best rating, then pricing tier. It is a short array and it can be empty when the market’s best offering carried none of them. Appetite appears as a rank reason only when the line the cell matched is also the line that was ranked, and the value the comparator read agrees with the cell. When it does not, the appetite is still returned in the appetite block. It explains the match; it did not drive the position. Product count is not a reason, and never will be: the comparator does not read it. The products array itself shows the served lines in scope. Commission never affects ordering. commission_new and commission_renewal are display-only disclosed values. Nothing about the rank of a market is a function of what it pays.

What is never defaulted

  • am_best_rating is null when the corpus holds no rating. Null means not known. It is not “unrated”, and it is not a low rating.
  • commission_new and commission_renewal are omitted when the rate was never disclosed. An omitted rate is not a zero percent commission. Coalescing it to 0 will misrank a market in your own display and misinform a producer.
  • stale is tri-state: true (assessed, out of date), false (assessed, current), and absent (never assessed). Absent and false are different facts. Treating absent as false presents an unassessed schedule as verified-current.
  • source_blob_ref and extraction_run_id are omitted when lineage was not recorded. They are never fabricated.

When the answer is partial or empty

Two fields carry the limits of the answer, and they say different things. no_coverage carries the reason nothing could be answered: the query resolved to no class, line, or state; the organization has no active catalog connection; or every requested line is outside this catalog’s scope. It is also set when the class resolved but the corpus holds no appetite data for it, and in that case markets are still listed by line and state and the message says so. Surface the message; do not branch on its presence alone.
lines_not_assessed is a scope caveat that rides alongside results. This catalog serves commercial lines and surety, while the taxonomy also resolves personal lines, so a mixed request is answered for the lines in scope and told which ones were left out:
When every requested line is out of scope there is nothing to answer, so no_coverage is set too and recommendations is empty.

Connections

Like the rest of the catalog family, the read is scoped to one data connection. Organizations with a single active connection can omit connection_id; organizations with more than one must pass it, and an ambiguous request returns catalog_connection_ambiguous with a 400. A connection_id that is not an active connection for the organization returns 404 not_found rather than an empty answer, so a pinned client that loses its connection fails loud. An organization with no active connection gets a 200 whose no_coverage says exactly that, rather than an answer that reads as “no market wants this”.

Example

The second market is the one worth reading twice. It has no AM Best rating on file, its general liability row discloses a renewal rate but no new-business rate, and its schedule has been assessed and found out of date. Three separate absences, three different meanings, and not one of them is a zero. Rendering am_best_rating as 0, or commission_new as 0%, would put a number in front of a producer that no document ever stated. Its rank_reasons also carry only one entry, because the comparator had only one signal to read on it. That is the contract working: the array is a record of what was read, not a summary written after the fact.

Authorizations

Authorization
string
header
required

Send the credential as Authorization: Bearer <key>.

Scoped partner credentials use the exclusive nxsk_v1_... namespace. Each scoped key is bound at issuance to one organization, one canonical named environment, an explicit engine set, and a least-privilege capability set. A malformed, unknown, rotated, or revoked nxsk_ key fails closed and is never retried as a legacy key.

Capabilities used by this API are runs:write, runs:read, engines:read, catalog:read, webhooks:manage, runs:defensibility:read, runs:test, conversations:use, and conversations:export. Operation descriptions name the required capability. Grandfathered nx_live_... and nx_test_... keys retain their existing broad access during the compatibility window.

Query Parameters

q
string

Free-text description of the risk, for example roofing contractor in Colorado needing general liability. State, lines of business, and business class are all scanned out of it. At least one of q, naics, state, or lob must be present.

Maximum string length: 500
naics
string

NAICS business-class code, at any grain (23, 2381, 238160). A code with no entry in the keyword bridge still resolves and is echoed back with no keyword, rather than being dropped.

Maximum string length: 10
state
string

US state as a two-letter code or a full name (CO and Colorado both resolve). Unresolvable text leaves resolved.state null and puts state in missing_context; it is not an error.

Maximum string length: 50
lob
string[]

Line of business, repeatable (?lob=general+liability&lob=workers+comp). Each value is classified through the shared line taxonomy, so both canonical codes and everyday phrasing work. At most 10 values, each at most 200 characters.

Maximum array length: 10
Maximum string length: 200
employees
integer

Applicant employee count. Used only for positive exclusion: a product is dropped when its own stated employee band excludes the number. Absent band data never excludes.

Required range: x >= 1
premium_band
integer

Estimated annual premium in whole US dollars. Used only for positive exclusion against a product's stated premium band, the same way employees is used.

Required range: x >= 1
connection_id
string<uuid>

Pin the browse to one active data connection. Optional when the org has a single active connection; required when it has more than one (an ambiguous request returns catalog_connection_ambiguous).

Response

The resolution, its corpus coverage, and up to 5 ranked markets. A question the corpus cannot answer is still a 200: the honest answer is the echo plus missing_context plus no_coverage, with an empty recommendations array.

query
object
required

The request the answer was computed for, echoed back. Fields the caller did not send are omitted.

resolved
object
required

What the platform understood the query to mean. Same shape the taxonomy resolver serves.

missing_context
enum<string>[]
required

Critical inputs the resolution could not supply, in a fixed order. The consumer decides whether to ask a clarifying question; the platform still answers with what it has.

Available options:
state,
line_of_business,
business_class
coverage
object
required

What the served corpus can actually answer for this resolution, including the honest zeroes.

recommendations
object[]
required

Ranked markets, best first, at most 5. Always present; an empty array is the honest answer for a question with no matching market.

Maximum array length: 5
no_coverage
string

Present when the corpus could answer nothing (the query resolved to no class, line, or state; the org has no active catalog connection; every requested line is outside this catalog's scope), and also when the class resolved but no served appetite data exists for it. In that last case markets are still listed by line and state, and the message says so. Read the message rather than branching on presence alone.

lines_not_assessed
string[]

Requested lines this catalog could not assess, named in the labels the resolution produced. This catalog serves commercial lines and surety while the taxonomy also resolves personal lines, so a mixed request is answered for the lines in scope and this array says which ones were left out. It is a partial-scope caveat that rides alongside results, not a terminal state. When EVERY requested line is out of scope, no_coverage is set as well and recommendations is empty.