> ## Documentation Index
> Fetch the complete documentation index at: https://docs.signa.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Check a Listing

> Screen a whole product listing for trademark conflicts (beta)

<Warning>
  **Beta.** Requires the `screening:read` scope — granted to design partners
  during beta. Not legal advice.
</Warning>

<Warning>
  **Known limitation (beta).** The current build over-flags listings that
  contain common dictionary words, so it returns `caution` for the large
  majority of ordinary listings. A verdict-separation redesign is in progress
  to restore precision. Until it lands, treat a `caution` verdict as no-signal,
  not evidence of risk, and do not rely on the endpoint for automated
  enforcement decisions. Real-world validation against the full production
  register superseded the earlier precision numbers measured on a small
  synthetic test index.
</Warning>

## Overview

`POST /v1/screening/listings` answers *"is this LISTING risky?"* — one level up
from [screening a name](/api-reference/screening/screen). You send the
commerce text of a product listing (title, brand, description, keywords) and it:

1. **Extracts** candidate marks deterministically (no LLM at request time), with
   provenance (source field + character span per candidate).
2. **Fans out** each candidate through the screening core.
3. **Aggregates** to one listing-level verdict (`clear` / `caution` /
   `high_risk`), deduplicating conflicts across candidates.

Billed **flat at 25 units per checked listing**, regardless of internal fan-out.

## Request

```json theme={null}
{
  "title": "Vintage AirPods Case — Handmade Leather",
  "brand": "LeatherCraft Co",
  "description": "Compatible with AirPods Pro. Genuine leather.",
  "keywords": ["airpods case", "leather", "handmade"],
  "nice_classes": [18],
  "jurisdictions": ["US"],
  "sensitivity": "standard"
}
```

Only `title` is required. Fields are **generic** — there are no marketplace-specific
fields (Etsy/Amazon mappings live in docs recipes, not the schema).

| Field           | Type                              | Notes                                                                     |
| --------------- | --------------------------------- | ------------------------------------------------------------------------- |
| `title`         | string                            | **Required.** Split on hard delimiters, then n-grammed.                   |
| `brand`         | string                            | Always screened, never suppressed.                                        |
| `description`   | string                            | High-precision markers only (quoted / ™®-adjacent / "compatible with X"). |
| `keywords`      | string\[]                         | Platform-neutral tags/keywords/search terms.                              |
| `category`      | string                            | Context only — never itself a candidate.                                  |
| `nice_classes`  | number\[]                         | Hard filter + class-conditioned descriptive suppression.                  |
| `jurisdictions` | string\[]                         | Territory codes (e.g. `US`, `EU`).                                        |
| `offices`       | string\[]                         | Registering office codes (e.g. `uspto`).                                  |
| `include`       | `live` \| `all`                   | `live` (default) keeps live marks.                                        |
| `sensitivity`   | `strict` \| `standard` \| `broad` | Primary-tier threshold; the title/keyword tier is always `strict`.        |

## Response

A bespoke envelope: a leading `listing` decision block, an evidence-first
`matches[]` payload, the back-compatible `data[]` of
`listing_candidate` (each conflict is byte-identical to a
[screening](/api-reference/screening/screen) hit), and a trailing
`suppressed[]` audit list explaining why candidates were not screened.

```json theme={null}
{
  "object": "list",
  "listing": {
    "verdict": "high_risk",
    "summary": { "high": 1, "medium": 0, "low": 0, "informational": 0 },
    "candidates_screened": 3,
    "rules_version": "screening-v1",
    "verdict_version": "listing-verdict-v8",
    "extractor_version": "listing-extractor-v1:…",
    "selector_version": "listing-selector-v1:…",
    "as_of": "2026-07-01T00:00:00Z",
    "warnings": [],
    "analysis_status": "complete",
    "auto_approve_eligible": false,
    "class_provenance": { "source": "provided_unverified", "confidence": null },
    "resolved_nice_classes": [18],
    "dictionaries_version": "…",
    "decision_asset_version": "a1b2c3d4e5f60718"
  },
  "matches": [
    {
      "object": "listing_match",
      "disposition": "actionable",
      "reason_codes": ["exact_mark", "live_status"],
      "rights_domain": "trademark",
      "goods_services_text_available": true,
      "candidate": { "term": "AirPods", "normalized": "airpods", "tier": "S", "field": "keywords" },
      "registration": { "object": "screening_hit", "risk_level": "high", "…": "…" }
    }
  ],
  "data": [
    {
      "object": "listing_candidate",
      "term": "AirPods",
      "normalized": "airpods",
      "tier": "S",
      "sources": [{ "field": "keywords", "span": [0, 7], "keyword_index": 0 }],
      "verdict": "high_risk",
      "nice_classes_used": [18],
      "hits": [ { "object": "screening_hit", "risk_level": "high", "…": "…" } ]
    }
  ],
  "suppressed": [
    { "object": "suppressed_candidate", "term": "vintage", "field": "title", "span": [0, 7], "reason": "jargon" }
  ],
  "has_more": false,
  "pagination": { "cursor": null },
  "request_id": "req_…"
}
```

### Reading the result

`analysis_status` is a closed completeness enum and is orthogonal to the
tri-state `verdict`. Automated approval must key off
`auto_approve_eligible` (equivalent to `verdict === "clear"` **and**
`analysis_status === "complete"`) and must not use `verdict` alone. Incomplete
or unsupported analysis is never auto-approvable.

`class_provenance` explains where the class set came from;
`provided_unverified` means the caller supplied it and Signa has not verified it
against a platform taxonomy. `resolved_nice_classes` is the listing-level set
actually screened. `dictionaries_version` and `decision_asset_version` make the
deterministic decision assets auditable. There is **no public `risk_score`**.

The GA scope is English-only. When the title and brand are majority non-Latin
(CJK, Thai, Arabic, or Cyrillic), the endpoint returns
`analysis_status: "unsupported_language"`, floors the verdict to `caution`, and
does not run the text through English screening.

### Safety guarantees

* **Never a false clear.** Any search-backend error → `503` (never a partial
  "clear"). If the candidate budget is exceeded, the listing can never be
  `clear` — it is forced to `caution` with a `candidate_budget_capped` warning.
* **Informational-only conflicts.** A tier-S *identical name in an unrelated
  class* (e.g. a famous word used descriptively) is surfaced with
  `"informational": true` but does not escalate the listing verdict.

## SDK

```typescript theme={null}
const result = await signa.screening.checkListing({
  title: "Vintage AirPods Case — Handmade Leather",
  nice_classes: [18],
});

if (!result.listing.auto_approve_eligible) {
  for (const match of result.matches) {
    console.log(match.disposition, match.reason_codes, match.registration);
  }
}
```
